webagents 0.1.12__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.
Files changed (96) hide show
  1. webagents/__init__.py +18 -0
  2. webagents/agents/__init__.py +13 -0
  3. webagents/agents/core/__init__.py +19 -0
  4. webagents/agents/core/base_agent.py +1834 -0
  5. webagents/agents/core/handoffs.py +293 -0
  6. webagents/agents/handoffs/__init__.py +0 -0
  7. webagents/agents/interfaces/__init__.py +0 -0
  8. webagents/agents/lifecycle/__init__.py +0 -0
  9. webagents/agents/skills/__init__.py +109 -0
  10. webagents/agents/skills/base.py +136 -0
  11. webagents/agents/skills/core/__init__.py +8 -0
  12. webagents/agents/skills/core/guardrails/__init__.py +0 -0
  13. webagents/agents/skills/core/llm/__init__.py +0 -0
  14. webagents/agents/skills/core/llm/anthropic/__init__.py +1 -0
  15. webagents/agents/skills/core/llm/litellm/__init__.py +10 -0
  16. webagents/agents/skills/core/llm/litellm/skill.py +538 -0
  17. webagents/agents/skills/core/llm/openai/__init__.py +1 -0
  18. webagents/agents/skills/core/llm/xai/__init__.py +1 -0
  19. webagents/agents/skills/core/mcp/README.md +375 -0
  20. webagents/agents/skills/core/mcp/__init__.py +15 -0
  21. webagents/agents/skills/core/mcp/skill.py +731 -0
  22. webagents/agents/skills/core/memory/__init__.py +11 -0
  23. webagents/agents/skills/core/memory/long_term_memory/__init__.py +10 -0
  24. webagents/agents/skills/core/memory/long_term_memory/memory_skill.py +639 -0
  25. webagents/agents/skills/core/memory/short_term_memory/__init__.py +9 -0
  26. webagents/agents/skills/core/memory/short_term_memory/skill.py +341 -0
  27. webagents/agents/skills/core/memory/vector_memory/skill.py +447 -0
  28. webagents/agents/skills/core/planning/__init__.py +9 -0
  29. webagents/agents/skills/core/planning/planner.py +343 -0
  30. webagents/agents/skills/ecosystem/__init__.py +0 -0
  31. webagents/agents/skills/ecosystem/crewai/__init__.py +1 -0
  32. webagents/agents/skills/ecosystem/database/__init__.py +1 -0
  33. webagents/agents/skills/ecosystem/filesystem/__init__.py +0 -0
  34. webagents/agents/skills/ecosystem/google/__init__.py +0 -0
  35. webagents/agents/skills/ecosystem/google/calendar/__init__.py +6 -0
  36. webagents/agents/skills/ecosystem/google/calendar/skill.py +306 -0
  37. webagents/agents/skills/ecosystem/n8n/__init__.py +0 -0
  38. webagents/agents/skills/ecosystem/openai_agents/__init__.py +0 -0
  39. webagents/agents/skills/ecosystem/web/__init__.py +0 -0
  40. webagents/agents/skills/ecosystem/zapier/__init__.py +0 -0
  41. webagents/agents/skills/robutler/__init__.py +11 -0
  42. webagents/agents/skills/robutler/auth/README.md +63 -0
  43. webagents/agents/skills/robutler/auth/__init__.py +17 -0
  44. webagents/agents/skills/robutler/auth/skill.py +354 -0
  45. webagents/agents/skills/robutler/crm/__init__.py +18 -0
  46. webagents/agents/skills/robutler/crm/skill.py +368 -0
  47. webagents/agents/skills/robutler/discovery/README.md +281 -0
  48. webagents/agents/skills/robutler/discovery/__init__.py +16 -0
  49. webagents/agents/skills/robutler/discovery/skill.py +230 -0
  50. webagents/agents/skills/robutler/kv/__init__.py +6 -0
  51. webagents/agents/skills/robutler/kv/skill.py +80 -0
  52. webagents/agents/skills/robutler/message_history/__init__.py +9 -0
  53. webagents/agents/skills/robutler/message_history/skill.py +270 -0
  54. webagents/agents/skills/robutler/messages/__init__.py +0 -0
  55. webagents/agents/skills/robutler/nli/__init__.py +13 -0
  56. webagents/agents/skills/robutler/nli/skill.py +687 -0
  57. webagents/agents/skills/robutler/notifications/__init__.py +5 -0
  58. webagents/agents/skills/robutler/notifications/skill.py +141 -0
  59. webagents/agents/skills/robutler/payments/__init__.py +41 -0
  60. webagents/agents/skills/robutler/payments/exceptions.py +255 -0
  61. webagents/agents/skills/robutler/payments/skill.py +610 -0
  62. webagents/agents/skills/robutler/storage/__init__.py +10 -0
  63. webagents/agents/skills/robutler/storage/files/__init__.py +9 -0
  64. webagents/agents/skills/robutler/storage/files/skill.py +445 -0
  65. webagents/agents/skills/robutler/storage/json/__init__.py +9 -0
  66. webagents/agents/skills/robutler/storage/json/skill.py +336 -0
  67. webagents/agents/skills/robutler/storage/kv/skill.py +88 -0
  68. webagents/agents/skills/robutler/storage.py +389 -0
  69. webagents/agents/tools/__init__.py +0 -0
  70. webagents/agents/tools/decorators.py +426 -0
  71. webagents/agents/tracing/__init__.py +0 -0
  72. webagents/agents/workflows/__init__.py +0 -0
  73. webagents/api/__init__.py +17 -0
  74. webagents/api/client.py +1207 -0
  75. webagents/api/types.py +253 -0
  76. webagents/scripts/__init__.py +0 -0
  77. webagents/server/__init__.py +28 -0
  78. webagents/server/context/__init__.py +0 -0
  79. webagents/server/context/context_vars.py +121 -0
  80. webagents/server/core/__init__.py +0 -0
  81. webagents/server/core/app.py +843 -0
  82. webagents/server/core/middleware.py +69 -0
  83. webagents/server/core/models.py +98 -0
  84. webagents/server/core/monitoring.py +59 -0
  85. webagents/server/endpoints/__init__.py +0 -0
  86. webagents/server/interfaces/__init__.py +0 -0
  87. webagents/server/middleware.py +330 -0
  88. webagents/server/models.py +92 -0
  89. webagents/server/monitoring.py +659 -0
  90. webagents/utils/__init__.py +0 -0
  91. webagents/utils/logging.py +359 -0
  92. webagents-0.1.12.dist-info/METADATA +99 -0
  93. webagents-0.1.12.dist-info/RECORD +96 -0
  94. webagents-0.1.12.dist-info/WHEEL +4 -0
  95. webagents-0.1.12.dist-info/entry_points.txt +2 -0
  96. webagents-0.1.12.dist-info/licenses/LICENSE +1 -0
@@ -0,0 +1,230 @@
1
+ """
2
+ DiscoverySkill - Simplified Robutler Platform Integration
3
+
4
+ Agent discovery skill for Robutler platform.
5
+ Provides intent-based agent discovery and intent publishing capabilities.
6
+ """
7
+
8
+ import os
9
+ from typing import Dict, Any, List
10
+ from dataclasses import dataclass
11
+
12
+ from webagents.agents.skills.base import Skill
13
+ from webagents.agents.tools.decorators import tool, prompt
14
+
15
+
16
+ @dataclass
17
+ class DiscoveryResult:
18
+ """Result from intent discovery operation"""
19
+ agent_id: str
20
+ intent: str
21
+ agent_description: str
22
+ similarity: float
23
+ url: str
24
+ rank: int
25
+
26
+
27
+ class DiscoverySkill(Skill):
28
+ """
29
+ Simplified agent discovery skill for Robutler platform
30
+
31
+ Key Features:
32
+ - Intent-based agent discovery via Portal API
33
+ - Intent publishing for agent registration
34
+ - Dynamic agent URL configuration for prompts
35
+
36
+ Configuration hierarchy for robutler_api_key:
37
+ 1. config.robutler_api_key (explicit configuration)
38
+ 2. agent.api_key (agent's API key)
39
+ 3. ROBUTLER_API_KEY environment variable
40
+ 4. SERVICE_TOKEN environment variable
41
+
42
+ Configuration hierarchy for agent_base_url:
43
+ 1. AGENTS_BASE_URL environment variable
44
+ 2. config.agent_base_url (explicit configuration)
45
+ 3. 'http://localhost:2224' (default for local development)
46
+ """
47
+
48
+ def __init__(self, config: Dict[str, Any] = None):
49
+ super().__init__(config, scope="all")
50
+
51
+ # Configuration
52
+ self.config = config or {}
53
+ self.enable_discovery = self.config.get('enable_discovery', True)
54
+
55
+ # Robutler platform configuration
56
+ self.robutler_api_url = (
57
+ os.getenv('ROBUTLER_INTERNAL_API_URL') or
58
+ os.getenv('ROBUTLER_API_URL') or
59
+ self.config.get('robutler_api_url') or
60
+ 'https://robutler.ai'
61
+ )
62
+
63
+ # Agent communication base URL configuration (for dynamic agent URLs in prompts)
64
+ self.agent_base_url = (
65
+ os.getenv('AGENTS_BASE_URL') or
66
+ self.config.get('agent_base_url') or
67
+ 'http://localhost:2224' # Default for local development (agents server)
68
+ )
69
+
70
+ # API key: config first (will be resolved in initialize)
71
+ self.robutler_api_key = self.config.get('robutler_api_key')
72
+
73
+ async def initialize(self, agent) -> None:
74
+ """Initialize DiscoverySkill"""
75
+ from webagents.utils.logging import get_logger, log_skill_event
76
+
77
+ self.agent = agent
78
+ self.logger = get_logger('skill.robutler.discovery', self.agent.name)
79
+
80
+ # Resolve Robutler API key using hierarchy: config -> agent.api_key -> env
81
+ if not self.robutler_api_key:
82
+ if hasattr(self.agent, 'api_key') and self.agent.api_key:
83
+ self.robutler_api_key = self.agent.api_key
84
+ self.logger.debug(f"Using agent's API key for Robutler platform")
85
+ elif os.getenv('ROBUTLER_API_KEY'):
86
+ self.robutler_api_key = os.getenv('ROBUTLER_API_KEY')
87
+ self.logger.debug(f"Using ROBUTLER_API_KEY from environment")
88
+ elif os.getenv('SERVICE_TOKEN'):
89
+ self.robutler_api_key = os.getenv('SERVICE_TOKEN')
90
+ self.logger.debug(f"Using SERVICE_TOKEN from environment")
91
+ else:
92
+ self.logger.warning("No API key configured for discovery")
93
+
94
+ log_skill_event(self.agent.name, 'discovery', 'initialized', {
95
+ 'enable_discovery': self.enable_discovery,
96
+ 'robutler_api_url': self.robutler_api_url,
97
+ 'agent_base_url': self.agent_base_url,
98
+ 'has_api_key': bool(self.robutler_api_key)
99
+ })
100
+
101
+ # ===== DISCOVERY TOOLS =====
102
+
103
+ @prompt(priority=20, scope="all")
104
+ def discovery_general_prompt(self, context: Any = None) -> str:
105
+ base_url = self.agent_base_url.rstrip('/')
106
+ return f"You are part of a network of AI agents working for their owners. Each agent has their own name and address. @agentname corresponds to {base_url}/agents/agentname. Use discovery_tool to discover other agents and their capabilities."
107
+
108
+ @tool(description="Discover agents and their capabilities across the Robutler network by intent. Make the intent as specific as possible. When presenting the results, make it simple and non-technical.", scope="all")
109
+ async def discovery_tool(self,
110
+ intent: str,
111
+ top_k: int = 10,
112
+ context=None) -> Dict[str, Any]:
113
+ """Discover agents by intent across the Robutler network"""
114
+ if not self.enable_discovery:
115
+ return {'success': False, 'error': 'Discovery disabled'}
116
+
117
+ if not self.robutler_api_key:
118
+ return {'success': False, 'error': 'API key not configured for discovery'}
119
+
120
+ try:
121
+ import aiohttp
122
+
123
+ # Clamp top_k between 1-50
124
+ limited_top_k = max(1, min(50, top_k))
125
+
126
+ async with aiohttp.ClientSession() as session:
127
+ async with session.post(
128
+ f"{self.robutler_api_url}/api/intents/search",
129
+ headers={
130
+ 'Authorization': f'Bearer {self.robutler_api_key}',
131
+ 'Content-Type': 'application/json',
132
+ },
133
+ json={
134
+ 'intent': intent.strip(),
135
+ 'top_k': limited_top_k,
136
+ }
137
+ ) as response:
138
+
139
+ if not response.ok:
140
+ raise Exception(f"Discovery API error: {response.status}")
141
+
142
+ result = await response.json()
143
+ results = result.get('data', {}).get('results', [])
144
+
145
+ return {
146
+ 'success': True,
147
+ 'intent': intent,
148
+ 'results_count': len(results),
149
+ 'results': [
150
+ {
151
+ 'agent_id': r.get('agent_id'),
152
+ 'intent': r.get('intent'),
153
+ 'description': r.get('agent_description'),
154
+ 'similarity': r.get('similarity'),
155
+ 'url': r.get('url'),
156
+ 'rank': r.get('rank'),
157
+ }
158
+ for r in results
159
+ ]
160
+ }
161
+
162
+ except Exception as e:
163
+ self.logger.error(f"Agent discovery failed: {e}")
164
+ return {
165
+ 'success': False,
166
+ 'intent': intent,
167
+ 'results_count': 0,
168
+ 'results': [],
169
+ 'error': str(e)
170
+ }
171
+
172
+ @tool(description="Publish agent intents to the platform", scope="owner")
173
+ async def publish_intents_tool(self,
174
+ intents: List[str],
175
+ description: str,
176
+ context=None) -> Dict[str, Any]:
177
+ """Publish agent intents to the Robutler platform"""
178
+ if not self.enable_discovery:
179
+ return {'success': False, 'error': 'Discovery disabled'}
180
+
181
+ if not self.robutler_api_key:
182
+ return {'success': False, 'error': 'API key not configured for discovery'}
183
+
184
+ try:
185
+ import aiohttp
186
+
187
+ # Get agent information
188
+ agent_id = getattr(self.agent, 'name', 'unknown')
189
+ agent_url = self.config.get('agent_url', f"https://robutler.ai/agents/{agent_id}")
190
+
191
+ # Prepare intent data
192
+ intents_data = [
193
+ {
194
+ 'intent': intent,
195
+ 'agent_id': agent_id,
196
+ 'description': description,
197
+ 'url': agent_url,
198
+ }
199
+ for intent in intents
200
+ ]
201
+
202
+ async with aiohttp.ClientSession() as session:
203
+ async with session.post(
204
+ f"{self.robutler_api_url}/api/intents/publish",
205
+ headers={
206
+ 'Authorization': f'Bearer {self.robutler_api_key}',
207
+ 'Content-Type': 'application/json',
208
+ },
209
+ json={'intents': intents_data}
210
+ ) as response:
211
+
212
+ if not response.ok:
213
+ raise Exception(f"Publish API error: {response.status}")
214
+
215
+ result = await response.json()
216
+
217
+ return {
218
+ 'success': True,
219
+ 'agent_id': agent_id,
220
+ 'published_intents': intents,
221
+ 'results': result
222
+ }
223
+
224
+ except Exception as e:
225
+ self.logger.error(f"Intent publishing failed: {e}")
226
+ return {'success': False, 'error': str(e)}
227
+
228
+ def get_dependencies(self) -> List[str]:
229
+ """Get skill dependencies"""
230
+ return ['aiohttp'] # Required for HTTP client
@@ -0,0 +1,6 @@
1
+ from .skill import KVSkill
2
+
3
+ __all__ = ["KVSkill"]
4
+
5
+
6
+
@@ -0,0 +1,80 @@
1
+ import os
2
+ from typing import Any, Dict, Optional
3
+
4
+ import httpx
5
+
6
+ from webagents.agents.skills.base import Skill
7
+ from webagents.agents.tools.decorators import tool
8
+ from webagents.server.context.context_vars import get_context
9
+
10
+
11
+ class KVSkill(Skill):
12
+ """Simple per-agent key-value storage via portal /api/kv.
13
+
14
+ Scope: owner-only tools to ensure only the agent owner can read/write.
15
+ """
16
+
17
+ def __init__(self, config: Optional[Dict[str, Any]] = None) -> None:
18
+ super().__init__(config or {}, scope="all")
19
+ self.portal_url = os.getenv('ROBUTLER_INTERNAL_API_URL') or os.getenv('ROBUTLER_API_URL', 'http://localhost:3000')
20
+
21
+ async def _resolve_agent_and_user(self) -> Optional[Dict[str, str]]:
22
+ try:
23
+ ctx = get_context()
24
+ agent = getattr(ctx, 'agent', None)
25
+ auth = getattr(ctx, 'auth', None) or (ctx and ctx.get('auth'))
26
+ agent_id = getattr(agent, 'id', None)
27
+ user_id = getattr(auth, 'user_id', None)
28
+ if agent_id and user_id:
29
+ return {"agent_id": agent_id, "user_id": user_id}
30
+ except Exception:
31
+ pass
32
+ return None
33
+
34
+ async def _headers(self) -> Dict[str, str]:
35
+ # Prefer agent API key for auth
36
+ api_key = getattr(self.agent, 'api_key', None)
37
+ return {"X-API-Key": api_key} if api_key else {}
38
+
39
+ @tool(description="Set a key to a string value (owner-only)", scope="owner")
40
+ async def kv_set(self, key: str, value: str, namespace: Optional[str] = None) -> str:
41
+ ids = await self._resolve_agent_and_user()
42
+ if not ids:
43
+ return "❌ Missing agent/user context"
44
+ body = {"agentId": ids["agent_id"], "key": key, "value": value, "namespace": namespace}
45
+ async with httpx.AsyncClient(timeout=15) as client:
46
+ resp = await client.post(f"{self.portal_url}/api/kv", json=body, headers=await self._headers())
47
+ if resp.status_code >= 400:
48
+ return f"❌ KV set failed: {resp.text}"
49
+ return "✅ Saved"
50
+
51
+ @tool(description="Get a string value by key (owner-only)", scope="owner")
52
+ async def kv_get(self, key: str, namespace: Optional[str] = None) -> str:
53
+ ids = await self._resolve_agent_and_user()
54
+ if not ids:
55
+ return ""
56
+ params = {"agentId": ids["agent_id"], "key": key}
57
+ if namespace:
58
+ params["namespace"] = namespace
59
+ async with httpx.AsyncClient(timeout=15) as client:
60
+ resp = await client.get(f"{self.portal_url}/api/kv", params=params, headers=await self._headers())
61
+ if resp.status_code >= 400:
62
+ return ""
63
+ data = resp.json()
64
+ return data.get('value') or ""
65
+
66
+ @tool(description="Delete a key (owner-only)", scope="owner")
67
+ async def kv_delete(self, key: str, namespace: Optional[str] = None) -> str:
68
+ ids = await self._resolve_agent_and_user()
69
+ if not ids:
70
+ return ""
71
+ params = {"agentId": ids["agent_id"], "key": key}
72
+ if namespace:
73
+ params["namespace"] = namespace
74
+ async with httpx.AsyncClient(timeout=15) as client:
75
+ resp = await client.delete(f"{self.portal_url}/api/kv", params=params, headers=await self._headers())
76
+ if resp.status_code >= 400:
77
+ return ""
78
+ return "🗑️ Deleted"
79
+
80
+
@@ -0,0 +1,9 @@
1
+ """
2
+ Message History Skill for Robutler Agents
3
+
4
+ Enables agents to save conversation messages to persistent storage in Robutler Portal.
5
+ """
6
+
7
+ from .skill import MessageHistorySkill
8
+
9
+ __all__ = ['MessageHistorySkill']
@@ -0,0 +1,270 @@
1
+ """
2
+ MessageHistorySkill - Save agent conversation messages to Robutler Portal
3
+
4
+ This skill enables agents to:
5
+ - Automatically save conversation messages on connection finalization
6
+ - Create and manage conversation threads
7
+ - Retrieve conversation history for context
8
+ - Support agent-to-agent and user-to-agent conversations
9
+ """
10
+
11
+ import os
12
+ import json
13
+ import asyncio
14
+ from typing import Dict, Any, List, Optional
15
+ from webagents.agents.skills.base import Skill
16
+ from webagents.agents.tools.decorators import tool, hook
17
+ from webagents.utils.logging import get_logger, log_skill_event
18
+
19
+
20
+ class MessageHistorySkill(Skill):
21
+ """
22
+ Skill for automatically saving agent conversation history in Robutler Portal
23
+
24
+ Features:
25
+ - Automatic message saving on connection finalization
26
+ - Create and manage conversation threads
27
+ - Support for agent-to-agent and user-to-agent conversations
28
+ - Retrieve conversation history for context
29
+ """
30
+
31
+ def __init__(self, config: Dict[str, Any] = None):
32
+ super().__init__(config, scope="all")
33
+
34
+ # Configuration
35
+ self.portal_url = config.get('portal_url') if config else None
36
+ self.api_key = config.get('api_key') if config else None
37
+
38
+ # Runtime state
39
+ self.current_conversation_id = None
40
+ self.agent_id = None
41
+ self.api_client = None
42
+ self.session_messages = []
43
+
44
+ async def initialize(self, agent: 'BaseAgent') -> None:
45
+ """Initialize the message history skill"""
46
+ from webagents.utils.logging import get_logger, log_skill_event
47
+
48
+ self.agent = agent
49
+ self.agent_id = getattr(agent, 'id', None) or getattr(agent, 'name', 'unknown')
50
+ self.logger = get_logger('skill.message_history', agent.name)
51
+
52
+ # Initialize API client
53
+ try:
54
+ from webagents.api.client import RobutlerClient
55
+
56
+ # Get API key from agent config, skill config, or environment
57
+ api_key = (
58
+ self.api_key or
59
+ getattr(agent, 'api_key', None) or
60
+ os.getenv('ROBUTLER_API_KEY')
61
+ )
62
+
63
+ if not api_key:
64
+ self.logger.warning("No API key available for MessageHistorySkill")
65
+ return
66
+
67
+ self.api_client = RobutlerClient(
68
+ api_key=api_key,
69
+ base_url=self.portal_url or os.getenv('ROBUTLER_API_URL', 'https://robutler.ai')
70
+ )
71
+
72
+ log_skill_event(agent.name, 'message_history', 'initialized', {
73
+ 'agent_id': self.agent_id
74
+ })
75
+
76
+ except ImportError:
77
+ self.logger.warning("RobutlerClient not available - MessageHistorySkill disabled")
78
+ except Exception as e:
79
+ self.logger.error(f"Failed to initialize MessageHistorySkill: {e}")
80
+
81
+ @tool(description="Create a new conversation thread for organizing messages")
82
+ async def create_conversation(
83
+ self,
84
+ title: str,
85
+ other_party_id: str,
86
+ other_party_type: str = "user",
87
+ visibility: str = "private"
88
+ ) -> str:
89
+ """Create a new conversation thread"""
90
+ if not self.api_client:
91
+ return "❌ Error: API client not initialized"
92
+
93
+ try:
94
+ import aiohttp
95
+
96
+ async with aiohttp.ClientSession() as session:
97
+ headers = {
98
+ 'Authorization': f'Bearer {self.api_client.api_key}',
99
+ 'Content-Type': 'application/json'
100
+ }
101
+
102
+ payload = {
103
+ 'operation': 'createChat',
104
+ 'agentId': self.agent_id,
105
+ 'title': title,
106
+ 'visibility': visibility,
107
+ 'otherPartyId': other_party_id,
108
+ 'otherPartyType': other_party_type
109
+ }
110
+
111
+ async with session.post(
112
+ f"{self.api_client.base_url}/api/chat",
113
+ headers=headers,
114
+ json=payload
115
+ ) as response:
116
+ if response.status == 200:
117
+ data = await response.json()
118
+ conversation_id = data.get('id')
119
+ self.current_conversation_id = conversation_id
120
+ return f"✅ Created conversation: {conversation_id}"
121
+ else:
122
+ error_text = await response.text()
123
+ return f"❌ Failed to create conversation: {response.status} - {error_text}"
124
+
125
+ except Exception as e:
126
+ self.logger.error(f"Error creating conversation: {e}")
127
+ return f"❌ Error creating conversation: {str(e)}"
128
+
129
+ @tool(description="Retrieve conversation history for context")
130
+ async def get_conversation_history(
131
+ self,
132
+ conversation_id: str = None,
133
+ limit: int = 50
134
+ ) -> str:
135
+ """Retrieve conversation history"""
136
+ if not self.api_client:
137
+ return "❌ Error: API client not initialized"
138
+
139
+ try:
140
+ chat_id = conversation_id or self.current_conversation_id
141
+ if not chat_id:
142
+ return "❌ Error: No conversation ID provided and no current conversation"
143
+
144
+ import aiohttp
145
+
146
+ async with aiohttp.ClientSession() as session:
147
+ headers = {
148
+ 'Authorization': f'Bearer {self.api_client.api_key}',
149
+ 'Content-Type': 'application/json'
150
+ }
151
+
152
+ params = {
153
+ 'operation': 'getMessagesByChatId',
154
+ 'chatId': chat_id
155
+ }
156
+
157
+ async with session.get(
158
+ f"{self.api_client.base_url}/api/messages",
159
+ headers=headers,
160
+ params=params
161
+ ) as response:
162
+ if response.status == 200:
163
+ data = await response.json()
164
+ messages = data.get('messages', [])
165
+
166
+ # Format messages for context
167
+ formatted_messages = []
168
+ for msg in messages[-limit:]: # Get last N messages
169
+ sender_info = f"{msg.get('senderType', 'unknown')}:{msg.get('senderId', 'unknown')}"
170
+ content = msg.get('parts', '')
171
+ if isinstance(content, list):
172
+ content = ' '.join(str(part) for part in content)
173
+ formatted_messages.append(f"[{sender_info}] {content}")
174
+
175
+ return "\n".join(formatted_messages)
176
+ else:
177
+ error_text = await response.text()
178
+ return f"❌ Failed to get conversation history: {response.status} - {error_text}"
179
+
180
+ except Exception as e:
181
+ self.logger.error(f"Error getting conversation history: {e}")
182
+ return f"❌ Error getting conversation history: {str(e)}"
183
+
184
+ @tool(description="Set the current active conversation")
185
+ async def set_current_conversation(self, conversation_id: str) -> str:
186
+ """Set the current active conversation"""
187
+ self.current_conversation_id = conversation_id
188
+ return f"✅ Set current conversation to: {conversation_id}"
189
+
190
+ @hook("on_message")
191
+ async def capture_session_messages(self, context):
192
+ """Capture messages during the session for later saving"""
193
+ try:
194
+ # Extract message information from context
195
+ messages = getattr(context, 'messages', [])
196
+ if messages:
197
+ # Store the full message exchange for this session
198
+ self.session_messages = messages.copy()
199
+ self.logger.debug(f"Captured {len(messages)} messages for session")
200
+
201
+ except Exception as e:
202
+ self.logger.warning(f"Failed to capture session messages: {e}")
203
+
204
+ return context
205
+
206
+ @hook("finalize_connection")
207
+ async def save_session_messages(self, context):
208
+ """Automatically save all session messages when connection finalizes"""
209
+ if not self.api_client or not self.session_messages or not self.current_conversation_id:
210
+ return context
211
+
212
+ try:
213
+ # Prepare messages for saving
214
+ messages_to_save = []
215
+
216
+ for i, msg in enumerate(self.session_messages):
217
+ # Determine sender info based on message role
218
+ if msg.get('role') == 'user':
219
+ sender_id = "user_session" # Default user ID - could be enhanced
220
+ sender_type = "user"
221
+ elif msg.get('role') == 'assistant':
222
+ sender_id = self.agent_id
223
+ sender_type = "agent"
224
+ else:
225
+ continue # Skip system messages
226
+
227
+ message_data = {
228
+ 'id': f"msg_{self.current_conversation_id}_{i}_{hash(str(msg))}",
229
+ 'chatId': self.current_conversation_id,
230
+ 'role': msg.get('role'),
231
+ 'parts': msg.get('content', ''),
232
+ 'attachments': json.dumps([]),
233
+ 'senderId': sender_id,
234
+ 'senderType': sender_type
235
+ }
236
+ messages_to_save.append(message_data)
237
+
238
+ if messages_to_save:
239
+ # Save messages via API
240
+ import aiohttp
241
+
242
+ async with aiohttp.ClientSession() as session:
243
+ headers = {
244
+ 'Authorization': f'Bearer {self.api_client.api_key}',
245
+ 'Content-Type': 'application/json'
246
+ }
247
+
248
+ payload = {
249
+ 'operation': 'saveMessages',
250
+ 'messages': messages_to_save
251
+ }
252
+
253
+ async with session.post(
254
+ f"{self.api_client.base_url}/api/messages",
255
+ headers=headers,
256
+ json=payload
257
+ ) as response:
258
+ if response.status == 200:
259
+ self.logger.info(f"Successfully saved {len(messages_to_save)} messages to conversation {self.current_conversation_id}")
260
+ else:
261
+ error_text = await response.text()
262
+ self.logger.error(f"Failed to save messages: {response.status} - {error_text}")
263
+
264
+ # Clear session messages after saving
265
+ self.session_messages = []
266
+
267
+ except Exception as e:
268
+ self.logger.error(f"Error saving session messages: {e}")
269
+
270
+ return context
File without changes
@@ -0,0 +1,13 @@
1
+ """
2
+ NLI Skill - Natural Language Interface for Agent-to-Agent Communication
3
+
4
+ Provides HTTP-based communication between Robutler agents with natural language messages.
5
+ """
6
+
7
+ from .skill import NLISkill, NLICommunication, AgentEndpoint
8
+
9
+ __all__ = [
10
+ 'NLISkill',
11
+ 'NLICommunication',
12
+ 'AgentEndpoint'
13
+ ]