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.
- webagents/__init__.py +18 -0
- webagents/agents/__init__.py +13 -0
- webagents/agents/core/__init__.py +19 -0
- webagents/agents/core/base_agent.py +1834 -0
- webagents/agents/core/handoffs.py +293 -0
- webagents/agents/handoffs/__init__.py +0 -0
- webagents/agents/interfaces/__init__.py +0 -0
- webagents/agents/lifecycle/__init__.py +0 -0
- webagents/agents/skills/__init__.py +109 -0
- webagents/agents/skills/base.py +136 -0
- webagents/agents/skills/core/__init__.py +8 -0
- webagents/agents/skills/core/guardrails/__init__.py +0 -0
- webagents/agents/skills/core/llm/__init__.py +0 -0
- webagents/agents/skills/core/llm/anthropic/__init__.py +1 -0
- webagents/agents/skills/core/llm/litellm/__init__.py +10 -0
- webagents/agents/skills/core/llm/litellm/skill.py +538 -0
- webagents/agents/skills/core/llm/openai/__init__.py +1 -0
- webagents/agents/skills/core/llm/xai/__init__.py +1 -0
- webagents/agents/skills/core/mcp/README.md +375 -0
- webagents/agents/skills/core/mcp/__init__.py +15 -0
- webagents/agents/skills/core/mcp/skill.py +731 -0
- webagents/agents/skills/core/memory/__init__.py +11 -0
- webagents/agents/skills/core/memory/long_term_memory/__init__.py +10 -0
- webagents/agents/skills/core/memory/long_term_memory/memory_skill.py +639 -0
- webagents/agents/skills/core/memory/short_term_memory/__init__.py +9 -0
- webagents/agents/skills/core/memory/short_term_memory/skill.py +341 -0
- webagents/agents/skills/core/memory/vector_memory/skill.py +447 -0
- webagents/agents/skills/core/planning/__init__.py +9 -0
- webagents/agents/skills/core/planning/planner.py +343 -0
- webagents/agents/skills/ecosystem/__init__.py +0 -0
- webagents/agents/skills/ecosystem/crewai/__init__.py +1 -0
- webagents/agents/skills/ecosystem/database/__init__.py +1 -0
- webagents/agents/skills/ecosystem/filesystem/__init__.py +0 -0
- webagents/agents/skills/ecosystem/google/__init__.py +0 -0
- webagents/agents/skills/ecosystem/google/calendar/__init__.py +6 -0
- webagents/agents/skills/ecosystem/google/calendar/skill.py +306 -0
- webagents/agents/skills/ecosystem/n8n/__init__.py +0 -0
- webagents/agents/skills/ecosystem/openai_agents/__init__.py +0 -0
- webagents/agents/skills/ecosystem/web/__init__.py +0 -0
- webagents/agents/skills/ecosystem/zapier/__init__.py +0 -0
- webagents/agents/skills/robutler/__init__.py +11 -0
- webagents/agents/skills/robutler/auth/README.md +63 -0
- webagents/agents/skills/robutler/auth/__init__.py +17 -0
- webagents/agents/skills/robutler/auth/skill.py +354 -0
- webagents/agents/skills/robutler/crm/__init__.py +18 -0
- webagents/agents/skills/robutler/crm/skill.py +368 -0
- webagents/agents/skills/robutler/discovery/README.md +281 -0
- webagents/agents/skills/robutler/discovery/__init__.py +16 -0
- webagents/agents/skills/robutler/discovery/skill.py +230 -0
- webagents/agents/skills/robutler/kv/__init__.py +6 -0
- webagents/agents/skills/robutler/kv/skill.py +80 -0
- webagents/agents/skills/robutler/message_history/__init__.py +9 -0
- webagents/agents/skills/robutler/message_history/skill.py +270 -0
- webagents/agents/skills/robutler/messages/__init__.py +0 -0
- webagents/agents/skills/robutler/nli/__init__.py +13 -0
- webagents/agents/skills/robutler/nli/skill.py +687 -0
- webagents/agents/skills/robutler/notifications/__init__.py +5 -0
- webagents/agents/skills/robutler/notifications/skill.py +141 -0
- webagents/agents/skills/robutler/payments/__init__.py +41 -0
- webagents/agents/skills/robutler/payments/exceptions.py +255 -0
- webagents/agents/skills/robutler/payments/skill.py +610 -0
- webagents/agents/skills/robutler/storage/__init__.py +10 -0
- webagents/agents/skills/robutler/storage/files/__init__.py +9 -0
- webagents/agents/skills/robutler/storage/files/skill.py +445 -0
- webagents/agents/skills/robutler/storage/json/__init__.py +9 -0
- webagents/agents/skills/robutler/storage/json/skill.py +336 -0
- webagents/agents/skills/robutler/storage/kv/skill.py +88 -0
- webagents/agents/skills/robutler/storage.py +389 -0
- webagents/agents/tools/__init__.py +0 -0
- webagents/agents/tools/decorators.py +426 -0
- webagents/agents/tracing/__init__.py +0 -0
- webagents/agents/workflows/__init__.py +0 -0
- webagents/api/__init__.py +17 -0
- webagents/api/client.py +1207 -0
- webagents/api/types.py +253 -0
- webagents/scripts/__init__.py +0 -0
- webagents/server/__init__.py +28 -0
- webagents/server/context/__init__.py +0 -0
- webagents/server/context/context_vars.py +121 -0
- webagents/server/core/__init__.py +0 -0
- webagents/server/core/app.py +843 -0
- webagents/server/core/middleware.py +69 -0
- webagents/server/core/models.py +98 -0
- webagents/server/core/monitoring.py +59 -0
- webagents/server/endpoints/__init__.py +0 -0
- webagents/server/interfaces/__init__.py +0 -0
- webagents/server/middleware.py +330 -0
- webagents/server/models.py +92 -0
- webagents/server/monitoring.py +659 -0
- webagents/utils/__init__.py +0 -0
- webagents/utils/logging.py +359 -0
- webagents-0.1.12.dist-info/METADATA +99 -0
- webagents-0.1.12.dist-info/RECORD +96 -0
- webagents-0.1.12.dist-info/WHEEL +4 -0
- webagents-0.1.12.dist-info/entry_points.txt +2 -0
- webagents-0.1.12.dist-info/licenses/LICENSE +1 -0
@@ -0,0 +1,445 @@
|
|
1
|
+
"""
|
2
|
+
RobutlerFilesSkill - File Management with Harmonized API
|
3
|
+
Uses the new harmonized content API for cleaner and more efficient operations.
|
4
|
+
"""
|
5
|
+
|
6
|
+
import json
|
7
|
+
import os
|
8
|
+
import base64
|
9
|
+
import aiohttp
|
10
|
+
from typing import Dict, List, Any, Optional, Union
|
11
|
+
from datetime import datetime
|
12
|
+
|
13
|
+
from ....base import Skill
|
14
|
+
from webagents.agents.tools.decorators import tool
|
15
|
+
from webagents.api.client import RobutlerClient
|
16
|
+
from webagents.agents.skills.robutler.payments import pricing, PricingInfo
|
17
|
+
|
18
|
+
class RobutlerFilesSkill(Skill):
|
19
|
+
"""
|
20
|
+
Robutler portal file management skill using harmonized API.
|
21
|
+
|
22
|
+
Features:
|
23
|
+
- Download and store files from URLs
|
24
|
+
- Store files from base64 data
|
25
|
+
- List files with agent-based access
|
26
|
+
- Agent access is automatically handled by the API
|
27
|
+
|
28
|
+
Uses the new /api/content/agent endpoints for agent operations.
|
29
|
+
"""
|
30
|
+
|
31
|
+
def __init__(self, config: Optional[Dict[str, Any]] = None):
|
32
|
+
super().__init__(config)
|
33
|
+
self.portal_url = config.get('portal_url', 'http://localhost:3000') if config else 'http://localhost:3000'
|
34
|
+
# Base URL used by the chat frontend to serve public content
|
35
|
+
self.chat_base_url = (config.get('chat_base_url') if config else None) or os.getenv('ROBUTLER_CHAT_URL', 'http://localhost:3001')
|
36
|
+
self.api_key = config.get('api_key', os.getenv('ROBUTLER_API_KEY', 'rok_testapikey')) if config else os.getenv('ROBUTLER_API_KEY', 'rok_testapikey')
|
37
|
+
|
38
|
+
# Initialize RobutlerClient
|
39
|
+
self.client = RobutlerClient(
|
40
|
+
api_key=self.api_key,
|
41
|
+
base_url=self.portal_url
|
42
|
+
)
|
43
|
+
|
44
|
+
async def initialize(self, agent_reference):
|
45
|
+
"""Initialize with agent reference"""
|
46
|
+
await super().initialize(agent_reference)
|
47
|
+
self.agent = agent_reference
|
48
|
+
|
49
|
+
# Check if agent has its own API key
|
50
|
+
if hasattr(agent_reference, 'api_key') and agent_reference.api_key:
|
51
|
+
self.agent_api_key = agent_reference.api_key
|
52
|
+
|
53
|
+
# Debug logging for agent API key
|
54
|
+
agent_key_prefix = self.agent_api_key[:20] + "..." if len(self.agent_api_key) > 20 else self.agent_api_key
|
55
|
+
print(f"🔑 Storage skill using agent API key: {agent_key_prefix}")
|
56
|
+
|
57
|
+
# Create a separate client for agent operations
|
58
|
+
self.agent_client = RobutlerClient(
|
59
|
+
api_key=self.agent_api_key,
|
60
|
+
base_url=self.portal_url
|
61
|
+
)
|
62
|
+
else:
|
63
|
+
# Fall back to user API key
|
64
|
+
self.agent_api_key = self.api_key
|
65
|
+
self.agent_client = self.client
|
66
|
+
|
67
|
+
# Debug logging for fallback
|
68
|
+
user_key_prefix = self.api_key[:20] + "..." if len(self.api_key) > 20 else self.api_key
|
69
|
+
print(f"🔑 Storage skill using user API key (fallback): {user_key_prefix}")
|
70
|
+
|
71
|
+
async def cleanup(self):
|
72
|
+
"""Cleanup method to close client sessions"""
|
73
|
+
if self.client:
|
74
|
+
await self.client.close()
|
75
|
+
|
76
|
+
# Close agent client if it's different from the main client
|
77
|
+
if hasattr(self, 'agent_client') and self.agent_client != self.client:
|
78
|
+
await self.agent_client.close()
|
79
|
+
|
80
|
+
def _get_agent_name_from_context(self) -> str:
|
81
|
+
"""
|
82
|
+
Get the current agent name from context.
|
83
|
+
|
84
|
+
Returns:
|
85
|
+
Agent name (e.g., 'van-gogh') or empty string if not found
|
86
|
+
"""
|
87
|
+
try:
|
88
|
+
from webagents.server.context.context_vars import get_agent_name
|
89
|
+
|
90
|
+
# Use the utility function
|
91
|
+
agent_name = get_agent_name()
|
92
|
+
if agent_name:
|
93
|
+
return agent_name
|
94
|
+
|
95
|
+
# Fallback: try to get from agent instance
|
96
|
+
if hasattr(self, 'agent') and hasattr(self.agent, 'name'):
|
97
|
+
return self.agent.name or ''
|
98
|
+
|
99
|
+
return '' # Default to empty string
|
100
|
+
except Exception:
|
101
|
+
return '' # Fallback to empty string
|
102
|
+
|
103
|
+
def _rewrite_public_url(self, url: Optional[str]) -> Optional[str]:
|
104
|
+
"""Rewrite portal public content URLs to chat base URL.
|
105
|
+
Examples:
|
106
|
+
http://localhost:3000/api/content/public/.. -> http://localhost:3001/api/content/public/..
|
107
|
+
/api/content/public/... stays relative and gets chat base prefixed when rendered client-side
|
108
|
+
"""
|
109
|
+
if not url:
|
110
|
+
return url
|
111
|
+
try:
|
112
|
+
if url.startswith('/api/content/public'):
|
113
|
+
# Already relative; prefix with chat base for clarity
|
114
|
+
return f"{self.chat_base_url}{url}"
|
115
|
+
portal_prefix = f"{self.portal_url}/api/content/public"
|
116
|
+
if url.startswith(portal_prefix):
|
117
|
+
return url.replace(self.portal_url, self.chat_base_url, 1)
|
118
|
+
except Exception:
|
119
|
+
return url
|
120
|
+
return url
|
121
|
+
|
122
|
+
# @tool(scope="owner")
|
123
|
+
async def store_file_from_url(
|
124
|
+
self,
|
125
|
+
url: str,
|
126
|
+
filename: Optional[str] = None,
|
127
|
+
description: Optional[str] = None,
|
128
|
+
tags: Optional[List[str]] = None,
|
129
|
+
visibility: str = "private"
|
130
|
+
) -> str:
|
131
|
+
"""
|
132
|
+
A tool for downloading and storing a file from a URL. Never use this tool for files that you already own, e.g. URLs returned by list_files.
|
133
|
+
|
134
|
+
Args:
|
135
|
+
url: URL to download file from
|
136
|
+
filename: Optional custom filename (auto-detected if not provided)
|
137
|
+
description: Optional description of the file
|
138
|
+
tags: Optional list of tags for the file
|
139
|
+
visibility: File visibility - "public", "private", or "shared" (default: "private")
|
140
|
+
|
141
|
+
Returns:
|
142
|
+
JSON string with storage result
|
143
|
+
"""
|
144
|
+
try:
|
145
|
+
# Download file from URL
|
146
|
+
async with aiohttp.ClientSession() as session:
|
147
|
+
async with session.get(url) as response:
|
148
|
+
if response.status != 200:
|
149
|
+
return json.dumps({
|
150
|
+
"success": False,
|
151
|
+
"error": f"Failed to download file: HTTP {response.status}"
|
152
|
+
})
|
153
|
+
|
154
|
+
content_data = await response.read()
|
155
|
+
content_type = response.headers.get('content-type', 'application/octet-stream')
|
156
|
+
|
157
|
+
# Auto-detect filename if not provided
|
158
|
+
if not filename:
|
159
|
+
filename = url.split('/')[-1] or 'downloaded_file'
|
160
|
+
# Remove query parameters
|
161
|
+
filename = filename.split('?')[0]
|
162
|
+
|
163
|
+
# Get agent name for filename prefixing
|
164
|
+
agent_name = self._get_agent_name_from_context()
|
165
|
+
|
166
|
+
# Prefix filename with agent name if available
|
167
|
+
if agent_name and not filename.startswith(f"{agent_name}_"):
|
168
|
+
filename = f"{agent_name}_{filename}"
|
169
|
+
|
170
|
+
# Store file using RobutlerClient with new API
|
171
|
+
response = await self.client.upload_content(
|
172
|
+
filename=filename,
|
173
|
+
content_data=content_data,
|
174
|
+
content_type=content_type,
|
175
|
+
visibility=visibility,
|
176
|
+
description=description or f"File downloaded from {url} by {agent_name or 'agent'}",
|
177
|
+
tags=tags
|
178
|
+
)
|
179
|
+
|
180
|
+
if response.success and response.data:
|
181
|
+
return json.dumps({
|
182
|
+
"success": True,
|
183
|
+
"id": response.data.get('id'),
|
184
|
+
"filename": response.data.get('fileName'),
|
185
|
+
"url": self._rewrite_public_url(response.data.get('url')),
|
186
|
+
"size": response.data.get('size'),
|
187
|
+
"content_type": content_type,
|
188
|
+
"visibility": visibility,
|
189
|
+
"source_url": url
|
190
|
+
}, indent=2)
|
191
|
+
else:
|
192
|
+
return json.dumps({
|
193
|
+
"success": False,
|
194
|
+
"error": f"Upload failed: {response.error or response.message}"
|
195
|
+
})
|
196
|
+
|
197
|
+
except Exception as e:
|
198
|
+
return json.dumps({
|
199
|
+
"success": False,
|
200
|
+
"error": f"Failed to store file from URL: {str(e)}"
|
201
|
+
})
|
202
|
+
|
203
|
+
# @tool(scope="owner")
|
204
|
+
async def store_file_from_base64(
|
205
|
+
self,
|
206
|
+
filename: str,
|
207
|
+
base64_data: str,
|
208
|
+
content_type: str = "application/octet-stream",
|
209
|
+
description: Optional[str] = None,
|
210
|
+
tags: Optional[List[str]] = None,
|
211
|
+
visibility: str = "private"
|
212
|
+
) -> str:
|
213
|
+
"""
|
214
|
+
A tool for storing a file from base64 encoded data.
|
215
|
+
|
216
|
+
Args:
|
217
|
+
filename: Name of the file
|
218
|
+
base64_data: Base64 encoded file content
|
219
|
+
content_type: MIME type of the file
|
220
|
+
description: Optional description of the file
|
221
|
+
tags: Optional list of tags for the file
|
222
|
+
visibility: File visibility - "public", "private", or "shared" (default: "private")
|
223
|
+
|
224
|
+
Returns:
|
225
|
+
JSON string with storage result
|
226
|
+
"""
|
227
|
+
try:
|
228
|
+
# Decode base64 data
|
229
|
+
content_data = base64.b64decode(base64_data)
|
230
|
+
|
231
|
+
# Get agent name for filename prefixing
|
232
|
+
agent_name = self._get_agent_name_from_context()
|
233
|
+
|
234
|
+
# Prefix filename with agent name if available
|
235
|
+
if agent_name and not filename.startswith(f"{agent_name}_"):
|
236
|
+
filename = f"{agent_name}_{filename}"
|
237
|
+
|
238
|
+
# Store file using RobutlerClient with new API
|
239
|
+
response = await self.client.upload_content(
|
240
|
+
filename=filename,
|
241
|
+
content_data=content_data,
|
242
|
+
content_type=content_type,
|
243
|
+
visibility=visibility,
|
244
|
+
description=description or f"File uploaded from base64 data by {agent_name or 'agent'}",
|
245
|
+
tags=tags
|
246
|
+
)
|
247
|
+
|
248
|
+
if response.success and response.data:
|
249
|
+
return json.dumps({
|
250
|
+
"success": True,
|
251
|
+
"id": response.data.get('id'),
|
252
|
+
"filename": response.data.get('fileName'),
|
253
|
+
"url": self._rewrite_public_url(response.data.get('url')),
|
254
|
+
"size": response.data.get('size'),
|
255
|
+
"content_type": content_type,
|
256
|
+
"visibility": visibility
|
257
|
+
}, indent=2)
|
258
|
+
else:
|
259
|
+
return json.dumps({
|
260
|
+
"success": False,
|
261
|
+
"error": f"Upload failed: {response.error or response.message}"
|
262
|
+
})
|
263
|
+
|
264
|
+
except Exception as e:
|
265
|
+
return json.dumps({
|
266
|
+
"success": False,
|
267
|
+
"error": f"Failed to store file from base64: {str(e)}"
|
268
|
+
})
|
269
|
+
|
270
|
+
@tool
|
271
|
+
@pricing(credits_per_call=0.005)
|
272
|
+
async def list_files(
|
273
|
+
self,
|
274
|
+
scope: Optional[str] = None
|
275
|
+
) -> str:
|
276
|
+
"""
|
277
|
+
List files accessible by the current agent with scope-based filtering.
|
278
|
+
|
279
|
+
The behavior depends on who is calling:
|
280
|
+
- Agent owner calling "show all files" (scope=None): Returns all private + public agent files
|
281
|
+
- Agent owner calling "show public files" (scope="public"): Returns only public agent files
|
282
|
+
- Agent owner calling "show private files" (scope="private"): Returns only private agent files
|
283
|
+
- Non-owner calling: Always returns only public agent files regardless of scope
|
284
|
+
|
285
|
+
Args:
|
286
|
+
scope: Optional scope filter - "public", "private", or None (all files for owner)
|
287
|
+
|
288
|
+
Returns:
|
289
|
+
JSON string with file list based on scope and ownership
|
290
|
+
"""
|
291
|
+
try:
|
292
|
+
from webagents.server.context.context_vars import get_context
|
293
|
+
from webagents.utils.logging import get_logger
|
294
|
+
logger = get_logger('robutler_files')
|
295
|
+
|
296
|
+
# Get context for agent information and auth
|
297
|
+
context = get_context()
|
298
|
+
if not context:
|
299
|
+
return json.dumps({
|
300
|
+
"success": False,
|
301
|
+
"error": "Agent context not available"
|
302
|
+
})
|
303
|
+
|
304
|
+
# Debug context attributes
|
305
|
+
print(f"🔍 DEBUG: Context available, type: {type(context)}")
|
306
|
+
print(f"🔍 DEBUG: Context attributes: {[attr for attr in dir(context) if not attr.startswith('_')]}")
|
307
|
+
if hasattr(context, 'custom_data'):
|
308
|
+
print(f"🔍 DEBUG: Context custom_data keys: {list(context.custom_data.keys())}")
|
309
|
+
|
310
|
+
agent_name = self._get_agent_name_from_context()
|
311
|
+
|
312
|
+
# Determine if current user is the actual owner of the agent
|
313
|
+
# SECURITY: Only actual owners should see private content, not just ADMINs
|
314
|
+
is_owner = False
|
315
|
+
try:
|
316
|
+
print(f"🔍 DEBUG: Determining actual ownership...")
|
317
|
+
|
318
|
+
# Check if we have auth context with user info
|
319
|
+
current_user_id = None
|
320
|
+
if context.auth and hasattr(context.auth, 'user_id'):
|
321
|
+
current_user_id = context.auth.user_id
|
322
|
+
print(f"🔍 DEBUG: Current user ID from auth: {current_user_id}")
|
323
|
+
|
324
|
+
# Get agent owner ID
|
325
|
+
agent_owner_id = None
|
326
|
+
if hasattr(self.agent, 'owner_user_id'):
|
327
|
+
agent_owner_id = self.agent.owner_user_id
|
328
|
+
print(f"🔍 DEBUG: Agent owner ID: {agent_owner_id}")
|
329
|
+
elif hasattr(self.agent, 'userId'):
|
330
|
+
agent_owner_id = self.agent.userId
|
331
|
+
print(f"🔍 DEBUG: Agent userId: {agent_owner_id}")
|
332
|
+
|
333
|
+
# Check if current user is the actual owner
|
334
|
+
if current_user_id and agent_owner_id:
|
335
|
+
is_owner = current_user_id == agent_owner_id
|
336
|
+
print(f"🔍 DEBUG: Ownership check: {current_user_id} == {agent_owner_id} = {is_owner}")
|
337
|
+
else:
|
338
|
+
print(f"🔍 DEBUG: Missing user ID or agent owner ID, defaulting to non-owner")
|
339
|
+
is_owner = False
|
340
|
+
|
341
|
+
print(f"🔍 DEBUG: Final isOwner determination: {is_owner}")
|
342
|
+
except Exception as e:
|
343
|
+
print(f"🔍 DEBUG: Error determining ownership: {e}")
|
344
|
+
is_owner = False
|
345
|
+
|
346
|
+
# Build URL with query parameters
|
347
|
+
url = f"{self.portal_url}/api/content/agent"
|
348
|
+
params = []
|
349
|
+
|
350
|
+
# Add isOwner parameter for security filtering
|
351
|
+
params.append(f"isOwner={str(is_owner).lower()}")
|
352
|
+
|
353
|
+
# Add scope parameter for filtering based on ownership and visibility
|
354
|
+
if scope:
|
355
|
+
params.append(f"scope={scope}")
|
356
|
+
|
357
|
+
if params:
|
358
|
+
url += "?" + "&".join(params)
|
359
|
+
|
360
|
+
# Make request to new agent content endpoint
|
361
|
+
api_key_prefix = self.agent_api_key[:20] + "..." if len(self.agent_api_key) > 20 else self.agent_api_key
|
362
|
+
print(f"🔍 DEBUG: Calling /api/content/agent using API key: {api_key_prefix}")
|
363
|
+
print(f"🔍 DEBUG: Final URL: {url}")
|
364
|
+
print(f"🔍 DEBUG: isOwner parameter being sent: {is_owner}")
|
365
|
+
|
366
|
+
# Make request with agent API key
|
367
|
+
async with aiohttp.ClientSession() as session:
|
368
|
+
headers = {
|
369
|
+
"Authorization": f"Bearer {self.agent_api_key}",
|
370
|
+
"Content-Type": "application/json"
|
371
|
+
}
|
372
|
+
|
373
|
+
async with session.get(url, headers=headers) as response:
|
374
|
+
if response.status != 200:
|
375
|
+
error_text = await response.text()
|
376
|
+
logger.error(f"Agent content API error: {response.status} - {error_text}")
|
377
|
+
return json.dumps({
|
378
|
+
"success": False,
|
379
|
+
"error": f"Failed to list files: HTTP {response.status}"
|
380
|
+
})
|
381
|
+
|
382
|
+
data = await response.json()
|
383
|
+
logger.debug(f"API Response status: {response.status}")
|
384
|
+
logger.debug(f"API Response content count: {len(data.get('content', []))}")
|
385
|
+
logger.debug(f"API Response scope info: {data.get('scope', {})}")
|
386
|
+
|
387
|
+
# Log each file for debugging
|
388
|
+
for item in data.get('content', []):
|
389
|
+
logger.debug(f"File: {item.get('fileName')} - Visibility: {item.get('visibility')}")
|
390
|
+
|
391
|
+
# Extract relevant fields from response
|
392
|
+
files = []
|
393
|
+
for item in data.get('content', []):
|
394
|
+
files.append({
|
395
|
+
"id": item.get('id'),
|
396
|
+
"filename": item.get('fileName'),
|
397
|
+
"original_filename": item.get('originalFileName'),
|
398
|
+
"size": item.get('size'),
|
399
|
+
"uploaded_at": item.get('uploadedAt'),
|
400
|
+
"description": item.get('description'),
|
401
|
+
"content_type": item.get('contentType'),
|
402
|
+
"url": self._rewrite_public_url(item.get('url')),
|
403
|
+
"visibility": item.get('visibility'),
|
404
|
+
"tags": item.get('tags', [])
|
405
|
+
})
|
406
|
+
|
407
|
+
return json.dumps({
|
408
|
+
"success": True,
|
409
|
+
"agent_name": data.get('agent', {}).get('name', agent_name),
|
410
|
+
"total_files": len(files),
|
411
|
+
"files": files
|
412
|
+
}, indent=2)
|
413
|
+
|
414
|
+
except Exception as e:
|
415
|
+
logger.error(f"Error in list_files: {e}")
|
416
|
+
return json.dumps({
|
417
|
+
"success": False,
|
418
|
+
"error": f"Failed to list files: {str(e)}"
|
419
|
+
})
|
420
|
+
|
421
|
+
def get_skill_info(self) -> Dict[str, Any]:
|
422
|
+
"""Get comprehensive skill information"""
|
423
|
+
return {
|
424
|
+
"name": "RobutlerFilesSkill",
|
425
|
+
"description": "File management using harmonized content API",
|
426
|
+
"version": "1.2.0",
|
427
|
+
"capabilities": [
|
428
|
+
"Download and store files from URLs (owner scope only)",
|
429
|
+
"Store files from base64 data (owner scope only)",
|
430
|
+
"List agent-accessible files using new API",
|
431
|
+
"Automatic agent name prefixing for uploaded files",
|
432
|
+
"Integration with harmonized content API",
|
433
|
+
"Simplified agent access management"
|
434
|
+
],
|
435
|
+
"tools": [
|
436
|
+
"store_file_from_url",
|
437
|
+
"store_file_from_base64",
|
438
|
+
"list_files"
|
439
|
+
],
|
440
|
+
"config": {
|
441
|
+
"portal_url": self.portal_url,
|
442
|
+
"api_key_configured": bool(self.api_key),
|
443
|
+
"api_version": "harmonized"
|
444
|
+
}
|
445
|
+
}
|