intentkit 0.7.5.dev17__py3-none-any.whl → 0.7.5.dev19__py3-none-any.whl
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.
Potentially problematic release.
This version of intentkit might be problematic. Click here for more details.
- intentkit/__init__.py +1 -1
- intentkit/abstracts/graph.py +3 -3
- intentkit/config/config.py +0 -5
- intentkit/core/engine.py +160 -143
- intentkit/core/node.py +11 -11
- intentkit/models/llm.py +8 -8
- intentkit/skills/enso/__init__.py +1 -1
- intentkit/skills/enso/base.py +44 -17
- intentkit/skills/enso/best_yield.py +11 -17
- intentkit/skills/enso/networks.py +3 -2
- intentkit/skills/enso/prices.py +9 -9
- intentkit/skills/enso/route.py +27 -23
- intentkit/skills/enso/tokens.py +24 -21
- intentkit/skills/enso/wallet.py +71 -186
- intentkit/skills/venice_audio/input.py +1 -1
- intentkit/skills/venice_audio/venice_audio.py +3 -3
- {intentkit-0.7.5.dev17.dist-info → intentkit-0.7.5.dev19.dist-info}/METADATA +1 -1
- {intentkit-0.7.5.dev17.dist-info → intentkit-0.7.5.dev19.dist-info}/RECORD +20 -20
- {intentkit-0.7.5.dev17.dist-info → intentkit-0.7.5.dev19.dist-info}/WHEEL +0 -0
- {intentkit-0.7.5.dev17.dist-info → intentkit-0.7.5.dev19.dist-info}/licenses/LICENSE +0 -0
intentkit/core/node.py
CHANGED
|
@@ -81,10 +81,10 @@ class PreModelNode(RunnableCallable):
|
|
|
81
81
|
self.func_accepts_config = True
|
|
82
82
|
|
|
83
83
|
def _parse_input(
|
|
84
|
-
self,
|
|
84
|
+
self, state: AgentState
|
|
85
85
|
) -> tuple[list[AnyMessage], dict[str, Any]]:
|
|
86
|
-
messages =
|
|
87
|
-
context =
|
|
86
|
+
messages = state.get("messages")
|
|
87
|
+
context = state.get("context", {})
|
|
88
88
|
if messages is None or not isinstance(messages, list) or len(messages) == 0:
|
|
89
89
|
raise ValueError("Missing required field `messages` in the input.")
|
|
90
90
|
return messages, context
|
|
@@ -107,13 +107,13 @@ class PreModelNode(RunnableCallable):
|
|
|
107
107
|
def _func(self, AgentState) -> dict[str, Any]:
|
|
108
108
|
raise NotImplementedError("Not implemented yet")
|
|
109
109
|
|
|
110
|
-
async def _afunc(self,
|
|
111
|
-
messages, context = self._parse_input(
|
|
110
|
+
async def _afunc(self, state: AgentState) -> dict[str, Any]:
|
|
111
|
+
messages, context = self._parse_input(state)
|
|
112
112
|
try:
|
|
113
113
|
_validate_chat_history(messages)
|
|
114
114
|
except ValueError as e:
|
|
115
115
|
logger.error(f"Invalid chat history: {e}")
|
|
116
|
-
logger.info(
|
|
116
|
+
logger.info(state)
|
|
117
117
|
# delete all messages
|
|
118
118
|
return {"messages": [RemoveMessage(REMOVE_ALL_MESSAGES)]}
|
|
119
119
|
if self.short_term_memory_strategy == "trim":
|
|
@@ -162,7 +162,7 @@ class PreModelNode(RunnableCallable):
|
|
|
162
162
|
return self._prepare_state_update(context, summarization_result)
|
|
163
163
|
except ValueError as e:
|
|
164
164
|
logger.error(f"Invalid chat history: {e}")
|
|
165
|
-
logger.info(
|
|
165
|
+
logger.info(state)
|
|
166
166
|
# delete all messages
|
|
167
167
|
return {"messages": [RemoveMessage(REMOVE_ALL_MESSAGES)]}
|
|
168
168
|
raise ValueError(
|
|
@@ -175,15 +175,15 @@ class PostModelNode(RunnableCallable):
|
|
|
175
175
|
super().__init__(self._func, self._afunc, name="post_model_node", trace=False)
|
|
176
176
|
self.func_accepts_config = True
|
|
177
177
|
|
|
178
|
-
def _func(self,
|
|
178
|
+
def _func(self, state: AgentState) -> dict[str, Any]:
|
|
179
179
|
raise NotImplementedError("Not implemented yet")
|
|
180
180
|
|
|
181
|
-
async def _afunc(self,
|
|
181
|
+
async def _afunc(self, state: AgentState) -> dict[str, Any]:
|
|
182
182
|
runtime = get_runtime(AgentContext)
|
|
183
183
|
context = runtime.context
|
|
184
|
-
logger.debug(f"Running PostModelNode, input: {
|
|
184
|
+
logger.debug(f"Running PostModelNode, input: {state}, context: {context}")
|
|
185
185
|
state_update = {}
|
|
186
|
-
messages =
|
|
186
|
+
messages = state.get("messages")
|
|
187
187
|
if messages is None or not isinstance(messages, list) or len(messages) == 0:
|
|
188
188
|
raise ValueError("Missing required field `messages` in the input.")
|
|
189
189
|
payer = context.payer
|
intentkit/models/llm.py
CHANGED
|
@@ -12,7 +12,7 @@ from intentkit.models.base import Base
|
|
|
12
12
|
from intentkit.models.db import get_session
|
|
13
13
|
from intentkit.models.redis import get_redis
|
|
14
14
|
from intentkit.utils.error import IntentKitLookUpError
|
|
15
|
-
from
|
|
15
|
+
from langchain.chat_models.base import BaseChatModel
|
|
16
16
|
from pydantic import BaseModel, ConfigDict, Field
|
|
17
17
|
from sqlalchemy import Boolean, Column, DateTime, Integer, Numeric, String, func, select
|
|
18
18
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
@@ -344,7 +344,7 @@ class LLMModel(BaseModel):
|
|
|
344
344
|
return model_info
|
|
345
345
|
|
|
346
346
|
# This will be implemented by subclasses to return the appropriate LLM instance
|
|
347
|
-
async def create_instance(self, config: Any) ->
|
|
347
|
+
async def create_instance(self, config: Any) -> BaseChatModel:
|
|
348
348
|
"""Create and return the LLM instance based on the configuration."""
|
|
349
349
|
raise NotImplementedError("Subclasses must implement create_instance")
|
|
350
350
|
|
|
@@ -362,7 +362,7 @@ class LLMModel(BaseModel):
|
|
|
362
362
|
class OpenAILLM(LLMModel):
|
|
363
363
|
"""OpenAI LLM configuration."""
|
|
364
364
|
|
|
365
|
-
async def create_instance(self, config: Any) ->
|
|
365
|
+
async def create_instance(self, config: Any) -> BaseChatModel:
|
|
366
366
|
"""Create and return a ChatOpenAI instance."""
|
|
367
367
|
from langchain_openai import ChatOpenAI
|
|
368
368
|
|
|
@@ -398,7 +398,7 @@ class OpenAILLM(LLMModel):
|
|
|
398
398
|
class DeepseekLLM(LLMModel):
|
|
399
399
|
"""Deepseek LLM configuration."""
|
|
400
400
|
|
|
401
|
-
async def create_instance(self, config: Any) ->
|
|
401
|
+
async def create_instance(self, config: Any) -> BaseChatModel:
|
|
402
402
|
"""Create and return a ChatDeepseek instance."""
|
|
403
403
|
|
|
404
404
|
from langchain_deepseek import ChatDeepSeek
|
|
@@ -431,7 +431,7 @@ class DeepseekLLM(LLMModel):
|
|
|
431
431
|
class XAILLM(LLMModel):
|
|
432
432
|
"""XAI (Grok) LLM configuration."""
|
|
433
433
|
|
|
434
|
-
async def create_instance(self, config: Any) ->
|
|
434
|
+
async def create_instance(self, config: Any) -> BaseChatModel:
|
|
435
435
|
"""Create and return a ChatXAI instance."""
|
|
436
436
|
|
|
437
437
|
from langchain_xai import ChatXAI
|
|
@@ -463,7 +463,7 @@ class XAILLM(LLMModel):
|
|
|
463
463
|
class EternalLLM(LLMModel):
|
|
464
464
|
"""Eternal AI LLM configuration."""
|
|
465
465
|
|
|
466
|
-
async def create_instance(self, config: Any) ->
|
|
466
|
+
async def create_instance(self, config: Any) -> BaseChatModel:
|
|
467
467
|
"""Create and return a ChatOpenAI instance configured for Eternal AI."""
|
|
468
468
|
from langchain_openai import ChatOpenAI
|
|
469
469
|
|
|
@@ -495,7 +495,7 @@ class EternalLLM(LLMModel):
|
|
|
495
495
|
class ReigentLLM(LLMModel):
|
|
496
496
|
"""Reigent LLM configuration."""
|
|
497
497
|
|
|
498
|
-
async def create_instance(self, config: Any) ->
|
|
498
|
+
async def create_instance(self, config: Any) -> BaseChatModel:
|
|
499
499
|
"""Create and return a ChatOpenAI instance configured for Reigent."""
|
|
500
500
|
from langchain_openai import ChatOpenAI
|
|
501
501
|
|
|
@@ -517,7 +517,7 @@ class ReigentLLM(LLMModel):
|
|
|
517
517
|
class VeniceLLM(LLMModel):
|
|
518
518
|
"""Venice LLM configuration."""
|
|
519
519
|
|
|
520
|
-
async def create_instance(self, config: Any) ->
|
|
520
|
+
async def create_instance(self, config: Any) -> BaseChatModel:
|
|
521
521
|
"""Create and return a ChatOpenAI instance configured for Venice."""
|
|
522
522
|
from langchain_openai import ChatOpenAI
|
|
523
523
|
|
intentkit/skills/enso/base.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
from decimal import Decimal
|
|
1
2
|
from typing import Optional, Type
|
|
2
3
|
|
|
3
|
-
from cdp import EvmServerAccount
|
|
4
4
|
from coinbase_agentkit import CdpEvmWalletProvider
|
|
5
5
|
from langchain.tools.base import ToolException
|
|
6
6
|
from pydantic import BaseModel, Field
|
|
@@ -9,10 +9,9 @@ from intentkit.abstracts.graph import AgentContext
|
|
|
9
9
|
from intentkit.abstracts.skill import SkillStoreABC
|
|
10
10
|
from intentkit.clients import CdpClient, get_cdp_client
|
|
11
11
|
from intentkit.skills.base import IntentKitSkill
|
|
12
|
-
from intentkit.utils.chain import ChainProvider,
|
|
12
|
+
from intentkit.utils.chain import ChainProvider, Network, network_to_id
|
|
13
13
|
|
|
14
14
|
base_url = "https://api.enso.finance"
|
|
15
|
-
default_chain_id = int(NetworkId.BaseMainnet)
|
|
16
15
|
|
|
17
16
|
|
|
18
17
|
class EnsoBaseTool(IntentKitSkill):
|
|
@@ -25,18 +24,6 @@ class EnsoBaseTool(IntentKitSkill):
|
|
|
25
24
|
description="The skill store for persisting data"
|
|
26
25
|
)
|
|
27
26
|
|
|
28
|
-
async def get_account(self, context: AgentContext) -> Optional[EvmServerAccount]:
|
|
29
|
-
"""Get the account object from the CDP client.
|
|
30
|
-
|
|
31
|
-
Args:
|
|
32
|
-
context: The skill context containing agent information.
|
|
33
|
-
|
|
34
|
-
Returns:
|
|
35
|
-
Optional[EvmServerAccount]: The account object if available.
|
|
36
|
-
"""
|
|
37
|
-
client: CdpClient = await get_cdp_client(context.agent.id, self.skill_store)
|
|
38
|
-
return await client.get_account()
|
|
39
|
-
|
|
40
27
|
async def get_wallet_provider(
|
|
41
28
|
self, context: AgentContext
|
|
42
29
|
) -> Optional[CdpEvmWalletProvider]:
|
|
@@ -51,6 +38,13 @@ class EnsoBaseTool(IntentKitSkill):
|
|
|
51
38
|
client: CdpClient = await get_cdp_client(context.agent.id, self.skill_store)
|
|
52
39
|
return await client.get_wallet_provider()
|
|
53
40
|
|
|
41
|
+
async def get_wallet_address(self, context: AgentContext) -> str:
|
|
42
|
+
client: CdpClient = await get_cdp_client(context.agent.id, self.skill_store)
|
|
43
|
+
provider_config = await client.get_provider_config()
|
|
44
|
+
if not provider_config.address:
|
|
45
|
+
raise ToolException("wallet address not found for agent")
|
|
46
|
+
return provider_config.address
|
|
47
|
+
|
|
54
48
|
def get_chain_provider(self, context: AgentContext) -> Optional[ChainProvider]:
|
|
55
49
|
return self.skill_store.get_system_config("chain_provider")
|
|
56
50
|
|
|
@@ -60,8 +54,7 @@ class EnsoBaseTool(IntentKitSkill):
|
|
|
60
54
|
return skill_config["main_tokens"]
|
|
61
55
|
return []
|
|
62
56
|
|
|
63
|
-
def
|
|
64
|
-
context = self.get_context()
|
|
57
|
+
def get_api_token(self, context: AgentContext) -> str:
|
|
65
58
|
skill_config = context.agent.skill_config(self.category)
|
|
66
59
|
api_key_provider = skill_config.get("api_key_provider")
|
|
67
60
|
if api_key_provider == "platform":
|
|
@@ -74,6 +67,40 @@ class EnsoBaseTool(IntentKitSkill):
|
|
|
74
67
|
f"Invalid API key provider: {api_key_provider}, or no api_token in config"
|
|
75
68
|
)
|
|
76
69
|
|
|
70
|
+
def resolve_chain_id(
|
|
71
|
+
self, context: AgentContext, chain_id: Optional[int] = None
|
|
72
|
+
) -> int:
|
|
73
|
+
if chain_id:
|
|
74
|
+
return chain_id
|
|
75
|
+
|
|
76
|
+
agent = context.agent
|
|
77
|
+
try:
|
|
78
|
+
network = Network(agent.network_id)
|
|
79
|
+
except ValueError as exc: # pragma: no cover - defensive
|
|
80
|
+
raise ToolException(
|
|
81
|
+
f"Unsupported network configured for agent: {agent.network_id}"
|
|
82
|
+
) from exc
|
|
83
|
+
|
|
84
|
+
network_id = network_to_id.get(network)
|
|
85
|
+
if network_id is None:
|
|
86
|
+
raise ToolException(
|
|
87
|
+
f"Unable to determine chain id for network: {agent.network_id}"
|
|
88
|
+
)
|
|
89
|
+
return int(network_id)
|
|
90
|
+
|
|
77
91
|
@property
|
|
78
92
|
def category(self) -> str:
|
|
79
93
|
return "enso"
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def format_amount_with_decimals(
|
|
97
|
+
amount: object, decimals: Optional[int]
|
|
98
|
+
) -> Optional[str]:
|
|
99
|
+
if amount is None or decimals is None:
|
|
100
|
+
return None
|
|
101
|
+
|
|
102
|
+
try:
|
|
103
|
+
value = Decimal(str(amount)) / (Decimal(10) ** decimals)
|
|
104
|
+
return format(value, "f")
|
|
105
|
+
except Exception: # pragma: no cover - defensive
|
|
106
|
+
return None
|
|
@@ -4,14 +4,7 @@ import httpx
|
|
|
4
4
|
from langchain.tools.base import ToolException
|
|
5
5
|
from pydantic import BaseModel, Field
|
|
6
6
|
|
|
7
|
-
from intentkit.skills.enso.base import
|
|
8
|
-
EnsoBaseTool,
|
|
9
|
-
base_url,
|
|
10
|
-
)
|
|
11
|
-
from intentkit.utils.chain import NetworkId
|
|
12
|
-
|
|
13
|
-
# Chain ID for Base Mainnet
|
|
14
|
-
BASE_CHAIN_ID = int(NetworkId.BaseMainnet)
|
|
7
|
+
from intentkit.skills.enso.base import EnsoBaseTool, base_url
|
|
15
8
|
|
|
16
9
|
|
|
17
10
|
class EnsoGetBestYieldInput(BaseModel):
|
|
@@ -21,9 +14,9 @@ class EnsoGetBestYieldInput(BaseModel):
|
|
|
21
14
|
"USDC",
|
|
22
15
|
description="Symbol of the token to find the best yield for (e.g., 'USDC', 'ETH', 'USDT')",
|
|
23
16
|
)
|
|
24
|
-
chain_id: int = Field(
|
|
25
|
-
|
|
26
|
-
description="The blockchain chain ID.
|
|
17
|
+
chain_id: int | None = Field(
|
|
18
|
+
None,
|
|
19
|
+
description="The blockchain chain ID. Defaults to the agent's configured network.",
|
|
27
20
|
)
|
|
28
21
|
top_n: int = Field(
|
|
29
22
|
5,
|
|
@@ -80,7 +73,7 @@ class EnsoGetBestYield(EnsoBaseTool):
|
|
|
80
73
|
async def _arun(
|
|
81
74
|
self,
|
|
82
75
|
token_symbol: str = "USDC",
|
|
83
|
-
chain_id: int =
|
|
76
|
+
chain_id: int | None = None,
|
|
84
77
|
top_n: int = 5,
|
|
85
78
|
**kwargs,
|
|
86
79
|
) -> EnsoGetBestYieldOutput:
|
|
@@ -89,7 +82,7 @@ class EnsoGetBestYield(EnsoBaseTool):
|
|
|
89
82
|
|
|
90
83
|
Args:
|
|
91
84
|
token_symbol (str): Symbol of the token to find the best yield for (default: USDC)
|
|
92
|
-
chain_id (int): The chain id of the network
|
|
85
|
+
chain_id (int | None): The chain id of the network. Defaults to the agent's configured network.
|
|
93
86
|
top_n (int): Number of top yield options to return
|
|
94
87
|
|
|
95
88
|
Returns:
|
|
@@ -99,16 +92,17 @@ class EnsoGetBestYield(EnsoBaseTool):
|
|
|
99
92
|
ToolException: If there's an error accessing the Enso API.
|
|
100
93
|
"""
|
|
101
94
|
context = self.get_context()
|
|
95
|
+
resolved_chain_id = self.resolve_chain_id(context, chain_id)
|
|
102
96
|
api_token = self.get_api_token(context)
|
|
103
97
|
|
|
104
98
|
if not api_token:
|
|
105
99
|
raise ToolException("No API token found for Enso Finance")
|
|
106
100
|
|
|
107
101
|
# Get the chain name for the given chain ID
|
|
108
|
-
chain_name = await self._get_chain_name(api_token,
|
|
102
|
+
chain_name = await self._get_chain_name(api_token, resolved_chain_id)
|
|
109
103
|
|
|
110
104
|
# Get all protocols on the specified chain
|
|
111
|
-
protocols = await self._get_protocols(api_token,
|
|
105
|
+
protocols = await self._get_protocols(api_token, resolved_chain_id)
|
|
112
106
|
|
|
113
107
|
# Collect all yield options from all protocols
|
|
114
108
|
all_yield_options = []
|
|
@@ -119,7 +113,7 @@ class EnsoGetBestYield(EnsoBaseTool):
|
|
|
119
113
|
|
|
120
114
|
# Get yield-bearing tokens for this protocol
|
|
121
115
|
tokens = await self._get_protocol_tokens(
|
|
122
|
-
api_token,
|
|
116
|
+
api_token, resolved_chain_id, protocol_slug, token_symbol
|
|
123
117
|
)
|
|
124
118
|
|
|
125
119
|
# Process tokens to extract yield options
|
|
@@ -170,7 +164,7 @@ class EnsoGetBestYield(EnsoBaseTool):
|
|
|
170
164
|
return EnsoGetBestYieldOutput(
|
|
171
165
|
best_options=top_options,
|
|
172
166
|
token_symbol=token_symbol,
|
|
173
|
-
chain_id=
|
|
167
|
+
chain_id=resolved_chain_id,
|
|
174
168
|
chain_name=chain_name,
|
|
175
169
|
)
|
|
176
170
|
|
|
@@ -7,8 +7,6 @@ from pydantic import BaseModel, Field
|
|
|
7
7
|
|
|
8
8
|
from .base import EnsoBaseTool, base_url
|
|
9
9
|
|
|
10
|
-
logger = logging.getLogger(__name__)
|
|
11
|
-
|
|
12
10
|
|
|
13
11
|
class EnsoGetNetworksInput(BaseModel):
|
|
14
12
|
"""
|
|
@@ -38,6 +36,9 @@ class EnsoGetNetworksOutput(BaseModel):
|
|
|
38
36
|
)
|
|
39
37
|
|
|
40
38
|
|
|
39
|
+
logger = logging.getLogger(__name__)
|
|
40
|
+
|
|
41
|
+
|
|
41
42
|
class EnsoGetNetworks(EnsoBaseTool):
|
|
42
43
|
"""
|
|
43
44
|
Tool for retrieving networks and their corresponding chainId, the output should be kept.
|
intentkit/skills/enso/prices.py
CHANGED
|
@@ -4,13 +4,11 @@ import httpx
|
|
|
4
4
|
from langchain.tools.base import ToolException
|
|
5
5
|
from pydantic import BaseModel, Field
|
|
6
6
|
|
|
7
|
-
from .base import EnsoBaseTool, base_url
|
|
7
|
+
from .base import EnsoBaseTool, base_url
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
class EnsoGetPricesInput(BaseModel):
|
|
11
|
-
chainId: int = Field(
|
|
12
|
-
default_chain_id, description="Blockchain chain ID of the token"
|
|
13
|
-
)
|
|
11
|
+
chainId: int | None = Field(None, description="Blockchain chain ID of the token")
|
|
14
12
|
address: str = Field(
|
|
15
13
|
"0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
|
|
16
14
|
description="Contract address of the token",
|
|
@@ -43,22 +41,21 @@ class EnsoGetPrices(EnsoBaseTool):
|
|
|
43
41
|
async def _arun(
|
|
44
42
|
self,
|
|
45
43
|
address: str,
|
|
46
|
-
chainId: int =
|
|
44
|
+
chainId: int | None = None,
|
|
47
45
|
**kwargs,
|
|
48
46
|
) -> EnsoGetPricesOutput:
|
|
49
47
|
"""
|
|
50
48
|
Asynchronous function to request the token price from the API.
|
|
51
49
|
|
|
52
50
|
Args:
|
|
53
|
-
chainId (int): The blockchain's chain ID.
|
|
51
|
+
chainId (int | None): The blockchain's chain ID. Defaults to the agent's configured network.
|
|
54
52
|
address (str): Contract address of the token.
|
|
55
53
|
|
|
56
54
|
Returns:
|
|
57
55
|
EnsoGetPricesOutput: Token price response or error message.
|
|
58
56
|
"""
|
|
59
|
-
url = f"{base_url}/api/v1/prices/{str(chainId)}/{address}"
|
|
60
|
-
|
|
61
57
|
context = self.get_context()
|
|
58
|
+
resolved_chain_id = self.resolve_chain_id(context, chainId)
|
|
62
59
|
api_token = self.get_api_token(context)
|
|
63
60
|
|
|
64
61
|
headers = {
|
|
@@ -68,7 +65,10 @@ class EnsoGetPrices(EnsoBaseTool):
|
|
|
68
65
|
|
|
69
66
|
async with httpx.AsyncClient() as client:
|
|
70
67
|
try:
|
|
71
|
-
response = await client.get(
|
|
68
|
+
response = await client.get(
|
|
69
|
+
f"{base_url}/api/v1/prices/{str(resolved_chain_id)}/{address}",
|
|
70
|
+
headers=headers,
|
|
71
|
+
)
|
|
72
72
|
response.raise_for_status()
|
|
73
73
|
json_dict = response.json()
|
|
74
74
|
|
intentkit/skills/enso/route.py
CHANGED
|
@@ -6,7 +6,7 @@ from pydantic import BaseModel, Field
|
|
|
6
6
|
|
|
7
7
|
from intentkit.skills.enso.networks import EnsoGetNetworks
|
|
8
8
|
|
|
9
|
-
from .base import EnsoBaseTool, base_url,
|
|
9
|
+
from .base import EnsoBaseTool, base_url, format_amount_with_decimals
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
class EnsoRouteShortcutInput(BaseModel):
|
|
@@ -18,9 +18,9 @@ class EnsoRouteShortcutInput(BaseModel):
|
|
|
18
18
|
False,
|
|
19
19
|
description="Whether to broadcast the transaction or not, this is false by default.",
|
|
20
20
|
)
|
|
21
|
-
chainId: int = Field(
|
|
22
|
-
|
|
23
|
-
description="(Optional) Chain ID of the network to execute the transaction on.
|
|
21
|
+
chainId: int | None = Field(
|
|
22
|
+
None,
|
|
23
|
+
description="(Optional) Chain ID of the network to execute the transaction on. Defaults to the agent's configured network.",
|
|
24
24
|
)
|
|
25
25
|
amountIn: list[int] = Field(
|
|
26
26
|
description="Amount of tokenIn to swap in wei, you should multiply user's requested value by token decimals."
|
|
@@ -162,7 +162,7 @@ class EnsoRouteShortcut(EnsoBaseTool):
|
|
|
162
162
|
amountIn: list[int],
|
|
163
163
|
tokenIn: list[str],
|
|
164
164
|
tokenOut: list[str],
|
|
165
|
-
chainId: int =
|
|
165
|
+
chainId: int | None = None,
|
|
166
166
|
broadcast_requested: bool = False,
|
|
167
167
|
**kwargs,
|
|
168
168
|
) -> EnsoRouteShortcutOutput:
|
|
@@ -173,7 +173,7 @@ class EnsoRouteShortcut(EnsoBaseTool):
|
|
|
173
173
|
amountIn (list[int]): Amount of tokenIn to swap in wei, you should multiply user's requested value by token decimals.
|
|
174
174
|
tokenIn (list[str]): Ethereum address of the token to swap or enter into a position from (For ETH, use 0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee).
|
|
175
175
|
tokenOut (list[str]): Ethereum address of the token to swap or enter into a position to (For ETH, use 0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee).
|
|
176
|
-
chainId (int): The chain id of the network to be used for swap, deposit and routing.
|
|
176
|
+
chainId (int | None): The chain id of the network to be used for swap, deposit and routing. Defaults to the agent's configured network.
|
|
177
177
|
broadcast_requested (bool): User should ask for broadcasting the transaction explicitly, otherwise it is always false.
|
|
178
178
|
|
|
179
179
|
Returns:
|
|
@@ -182,8 +182,9 @@ class EnsoRouteShortcut(EnsoBaseTool):
|
|
|
182
182
|
|
|
183
183
|
context = self.get_context()
|
|
184
184
|
agent_id = context.agent_id
|
|
185
|
+
resolved_chain_id = self.resolve_chain_id(context, chainId)
|
|
185
186
|
api_token = self.get_api_token(context)
|
|
186
|
-
|
|
187
|
+
wallet_address = await self.get_wallet_address(context)
|
|
187
188
|
|
|
188
189
|
async with httpx.AsyncClient() as client:
|
|
189
190
|
try:
|
|
@@ -193,9 +194,10 @@ class EnsoRouteShortcut(EnsoBaseTool):
|
|
|
193
194
|
)
|
|
194
195
|
|
|
195
196
|
if networks:
|
|
197
|
+
resolved_key = str(resolved_chain_id)
|
|
196
198
|
network_name = (
|
|
197
|
-
networks.get(
|
|
198
|
-
if networks.get(
|
|
199
|
+
networks.get(resolved_key).get("name")
|
|
200
|
+
if networks.get(resolved_key)
|
|
199
201
|
else None
|
|
200
202
|
)
|
|
201
203
|
if network_name is None:
|
|
@@ -204,12 +206,12 @@ class EnsoRouteShortcut(EnsoBaseTool):
|
|
|
204
206
|
).arun()
|
|
205
207
|
|
|
206
208
|
for network in networks.res:
|
|
207
|
-
if network.id ==
|
|
209
|
+
if network.id == resolved_chain_id:
|
|
208
210
|
network_name = network.name
|
|
209
211
|
|
|
210
212
|
if not network_name:
|
|
211
213
|
raise ToolException(
|
|
212
|
-
f"network name not found for chainId: {
|
|
214
|
+
f"network name not found for chainId: {resolved_chain_id}"
|
|
213
215
|
)
|
|
214
216
|
|
|
215
217
|
headers = {
|
|
@@ -242,13 +244,13 @@ class EnsoRouteShortcut(EnsoBaseTool):
|
|
|
242
244
|
|
|
243
245
|
# Prepare query parameters
|
|
244
246
|
params = EnsoRouteShortcutInput(
|
|
245
|
-
chainId=
|
|
247
|
+
chainId=resolved_chain_id,
|
|
246
248
|
amountIn=amountIn,
|
|
247
249
|
tokenIn=tokenIn,
|
|
248
250
|
tokenOut=tokenOut,
|
|
249
251
|
).model_dump(exclude_none=True)
|
|
250
252
|
|
|
251
|
-
params["fromAddress"] =
|
|
253
|
+
params["fromAddress"] = wallet_address
|
|
252
254
|
|
|
253
255
|
response = await client.get(url, headers=headers, params=params)
|
|
254
256
|
response.raise_for_status() # Raise HTTPError for non-2xx responses
|
|
@@ -256,10 +258,12 @@ class EnsoRouteShortcut(EnsoBaseTool):
|
|
|
256
258
|
|
|
257
259
|
res = EnsoRouteShortcutOutput(**json_dict)
|
|
258
260
|
res.network = network_name
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
261
|
+
decimals = token_decimals.get(tokenOut[0])
|
|
262
|
+
amount_out = format_amount_with_decimals(
|
|
263
|
+
json_dict.get("amountOut"), decimals
|
|
262
264
|
)
|
|
265
|
+
if amount_out is not None:
|
|
266
|
+
res.amountOut = amount_out
|
|
263
267
|
|
|
264
268
|
if broadcast_requested:
|
|
265
269
|
# Use the wallet provider to send the transaction
|
|
@@ -269,13 +273,13 @@ class EnsoRouteShortcut(EnsoBaseTool):
|
|
|
269
273
|
tx_data = json_dict.get("tx", {})
|
|
270
274
|
if tx_data:
|
|
271
275
|
# Send the transaction using the wallet provider
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
)
|
|
276
|
+
tx_params = {
|
|
277
|
+
"to": tx_data.get("to"),
|
|
278
|
+
"data": tx_data.get("data", "0x"),
|
|
279
|
+
"value": tx_data.get("value", 0),
|
|
280
|
+
"from": wallet_address,
|
|
281
|
+
}
|
|
282
|
+
tx_hash = wallet_provider.send_transaction(tx_params)
|
|
279
283
|
|
|
280
284
|
# Wait for transaction confirmation
|
|
281
285
|
wallet_provider.wait_for_transaction_receipt(tx_hash)
|
intentkit/skills/enso/tokens.py
CHANGED
|
@@ -4,11 +4,7 @@ import httpx
|
|
|
4
4
|
from langchain.tools.base import ToolException
|
|
5
5
|
from pydantic import BaseModel, Field
|
|
6
6
|
|
|
7
|
-
from intentkit.skills.enso.base import
|
|
8
|
-
EnsoBaseTool,
|
|
9
|
-
base_url,
|
|
10
|
-
default_chain_id,
|
|
11
|
-
)
|
|
7
|
+
from intentkit.skills.enso.base import EnsoBaseTool, base_url
|
|
12
8
|
|
|
13
9
|
# Actual Enso output types
|
|
14
10
|
# class UnderlyingToken(BaseModel):
|
|
@@ -50,9 +46,9 @@ from intentkit.skills.enso.base import (
|
|
|
50
46
|
|
|
51
47
|
|
|
52
48
|
class EnsoGetTokensInput(BaseModel):
|
|
53
|
-
chainId: int = Field(
|
|
54
|
-
|
|
55
|
-
description="The blockchain chain ID",
|
|
49
|
+
chainId: int | None = Field(
|
|
50
|
+
None,
|
|
51
|
+
description="The blockchain chain ID. Defaults to the agent's configured network.",
|
|
56
52
|
)
|
|
57
53
|
protocolSlug: str | None = Field(
|
|
58
54
|
None,
|
|
@@ -110,7 +106,10 @@ class TokenResponseCompact(BaseModel):
|
|
|
110
106
|
|
|
111
107
|
|
|
112
108
|
class EnsoGetTokensOutput(BaseModel):
|
|
113
|
-
res: list[TokenResponseCompact]
|
|
109
|
+
res: list[TokenResponseCompact] = Field(
|
|
110
|
+
default_factory=list,
|
|
111
|
+
description="List of token information entries",
|
|
112
|
+
)
|
|
114
113
|
|
|
115
114
|
|
|
116
115
|
class EnsoGetTokens(EnsoBaseTool):
|
|
@@ -138,7 +137,7 @@ class EnsoGetTokens(EnsoBaseTool):
|
|
|
138
137
|
|
|
139
138
|
async def _arun(
|
|
140
139
|
self,
|
|
141
|
-
chainId: int =
|
|
140
|
+
chainId: int | None = None,
|
|
142
141
|
protocolSlug: str | None = None,
|
|
143
142
|
**kwargs,
|
|
144
143
|
) -> EnsoGetTokensOutput:
|
|
@@ -156,15 +155,17 @@ class EnsoGetTokens(EnsoBaseTool):
|
|
|
156
155
|
|
|
157
156
|
context = self.get_context()
|
|
158
157
|
agent_id = context.agent_id
|
|
158
|
+
resolved_chain_id = self.resolve_chain_id(context, chainId)
|
|
159
159
|
api_token = self.get_api_token(context)
|
|
160
160
|
main_tokens = self.get_main_tokens(context)
|
|
161
|
+
main_tokens_upper = {token.upper() for token in main_tokens}
|
|
161
162
|
headers = {
|
|
162
163
|
"accept": "application/json",
|
|
163
164
|
"Authorization": f"Bearer {api_token}",
|
|
164
165
|
}
|
|
165
166
|
|
|
166
167
|
params = EnsoGetTokensInput(
|
|
167
|
-
chainId=
|
|
168
|
+
chainId=resolved_chain_id,
|
|
168
169
|
protocolSlug=protocolSlug,
|
|
169
170
|
).model_dump(exclude_none=True)
|
|
170
171
|
|
|
@@ -186,19 +187,21 @@ class EnsoGetTokens(EnsoBaseTool):
|
|
|
186
187
|
token_decimals = {}
|
|
187
188
|
|
|
188
189
|
# filter the main tokens from config or the ones that have apy assigned.
|
|
189
|
-
res = EnsoGetTokensOutput(
|
|
190
|
-
for item in json_dict
|
|
191
|
-
|
|
192
|
-
|
|
190
|
+
res = EnsoGetTokensOutput()
|
|
191
|
+
for item in json_dict.get("data", []):
|
|
192
|
+
symbol = item.get("symbol", "").upper()
|
|
193
|
+
has_apy = bool(item.get("apy"))
|
|
194
|
+
if has_apy or symbol in main_tokens_upper:
|
|
193
195
|
token_response = TokenResponseCompact(**item)
|
|
194
196
|
res.res.append(token_response)
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
197
|
+
if token_response.address:
|
|
198
|
+
token_decimals[token_response.address] = (
|
|
199
|
+
token_response.decimals
|
|
200
|
+
)
|
|
201
|
+
if token_response.underlyingTokens:
|
|
200
202
|
for u_token in token_response.underlyingTokens:
|
|
201
|
-
|
|
203
|
+
if u_token.address:
|
|
204
|
+
token_decimals[u_token.address] = u_token.decimals
|
|
202
205
|
|
|
203
206
|
await self.skill_store.save_agent_skill_data(
|
|
204
207
|
agent_id,
|