devduck 0.5.0__py3-none-any.whl → 0.5.2__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 devduck might be problematic. Click here for more details.
- devduck/__init__.py +612 -1004
- devduck/_version.py +2 -2
- devduck/agentcore_handler.py +76 -0
- devduck/tools/__init__.py +44 -1
- devduck/tools/_tray_app.py +8 -0
- devduck/tools/agentcore_agents.py +197 -0
- devduck/tools/agentcore_config.py +441 -0
- devduck/tools/agentcore_invoke.py +422 -0
- devduck/tools/agentcore_logs.py +320 -0
- devduck-0.5.2.dist-info/METADATA +415 -0
- {devduck-0.5.0.dist-info → devduck-0.5.2.dist-info}/RECORD +15 -10
- {devduck-0.5.0.dist-info → devduck-0.5.2.dist-info}/entry_points.txt +0 -1
- devduck-0.5.0.dist-info/METADATA +0 -554
- {devduck-0.5.0.dist-info → devduck-0.5.2.dist-info}/WHEEL +0 -0
- {devduck-0.5.0.dist-info → devduck-0.5.2.dist-info}/licenses/LICENSE +0 -0
- {devduck-0.5.0.dist-info → devduck-0.5.2.dist-info}/top_level.txt +0 -0
devduck/_version.py
CHANGED
|
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
|
|
|
28
28
|
commit_id: COMMIT_ID
|
|
29
29
|
__commit_id__: COMMIT_ID
|
|
30
30
|
|
|
31
|
-
__version__ = version = '0.5.
|
|
32
|
-
__version_tuple__ = version_tuple = (0, 5,
|
|
31
|
+
__version__ = version = '0.5.2'
|
|
32
|
+
__version_tuple__ = version_tuple = (0, 5, 2)
|
|
33
33
|
|
|
34
34
|
__commit_id__ = commit_id = None
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""DevDuck AgentCore Handler"""
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
import threading
|
|
6
|
+
from bedrock_agentcore.runtime import BedrockAgentCoreApp
|
|
7
|
+
|
|
8
|
+
# Configure for AgentCore deployment
|
|
9
|
+
os.environ["DEVDUCK_AUTO_START_SERVERS"] = "false"
|
|
10
|
+
os.environ["MODEL_PROVIDER"] = "bedrock"
|
|
11
|
+
|
|
12
|
+
from devduck import devduck
|
|
13
|
+
|
|
14
|
+
app = BedrockAgentCoreApp()
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@app.entrypoint
|
|
18
|
+
async def invoke(payload, context):
|
|
19
|
+
"""AgentCore entrypoint - streaming by default with async generator"""
|
|
20
|
+
mode = payload.get("mode", "streaming") # streaming (default), sync, async
|
|
21
|
+
|
|
22
|
+
query = payload.get("prompt", payload.get("text", ""))
|
|
23
|
+
if not query:
|
|
24
|
+
yield {"error": "No query provided"}
|
|
25
|
+
return
|
|
26
|
+
|
|
27
|
+
print(f"Mode: {mode}, Query: {query}")
|
|
28
|
+
|
|
29
|
+
agent = devduck.agent
|
|
30
|
+
|
|
31
|
+
if mode == "sync":
|
|
32
|
+
# Sync mode - return result directly (blocking)
|
|
33
|
+
try:
|
|
34
|
+
result = agent(query)
|
|
35
|
+
yield {"statusCode": 200, "response": str(result)}
|
|
36
|
+
except Exception as e:
|
|
37
|
+
print(f"Error in sync: {str(e)}")
|
|
38
|
+
yield {"statusCode": 500, "error": str(e)}
|
|
39
|
+
|
|
40
|
+
elif mode == "async":
|
|
41
|
+
# Async mode - fire and forget in background thread
|
|
42
|
+
task_id = app.add_async_task("devduck_processing", payload)
|
|
43
|
+
thread = threading.Thread(
|
|
44
|
+
target=lambda: _run_in_thread(agent, query, task_id), daemon=True
|
|
45
|
+
)
|
|
46
|
+
thread.start()
|
|
47
|
+
yield {"statusCode": 200, "task_id": task_id}
|
|
48
|
+
|
|
49
|
+
else:
|
|
50
|
+
# Streaming mode (default) - stream events as they happen
|
|
51
|
+
try:
|
|
52
|
+
stream = agent.stream_async(query)
|
|
53
|
+
async for event in stream:
|
|
54
|
+
print(event)
|
|
55
|
+
yield event
|
|
56
|
+
except Exception as e:
|
|
57
|
+
print(f"Error in streaming: {str(e)}")
|
|
58
|
+
yield {"error": str(e)}
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _run_in_thread(agent, query, task_id):
|
|
62
|
+
"""Run agent in background thread for async mode"""
|
|
63
|
+
try:
|
|
64
|
+
result = agent(query)
|
|
65
|
+
print(f"DevDuck result: {result}")
|
|
66
|
+
app.complete_async_task(task_id)
|
|
67
|
+
except Exception as e:
|
|
68
|
+
print(f"Error in async thread: {str(e)}")
|
|
69
|
+
try:
|
|
70
|
+
app.complete_async_task(task_id)
|
|
71
|
+
except:
|
|
72
|
+
pass
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
if __name__ == "__main__":
|
|
76
|
+
app.run()
|
devduck/tools/__init__.py
CHANGED
|
@@ -5,5 +5,48 @@ from .mcp_server import mcp_server
|
|
|
5
5
|
from .install_tools import install_tools
|
|
6
6
|
from .tray import tray
|
|
7
7
|
from .ambient import ambient
|
|
8
|
+
from .websocket import websocket
|
|
9
|
+
from .ipc import ipc
|
|
10
|
+
from .use_github import use_github
|
|
11
|
+
from .create_subagent import create_subagent
|
|
12
|
+
from .store_in_kb import store_in_kb
|
|
13
|
+
from .system_prompt import system_prompt
|
|
8
14
|
|
|
9
|
-
|
|
15
|
+
# AgentCore tools (conditionally available)
|
|
16
|
+
try:
|
|
17
|
+
from .agentcore_config import agentcore_config
|
|
18
|
+
from .agentcore_invoke import agentcore_invoke
|
|
19
|
+
from .agentcore_logs import agentcore_logs
|
|
20
|
+
from .agentcore_agents import agentcore_agents
|
|
21
|
+
|
|
22
|
+
__all__ = [
|
|
23
|
+
"tcp",
|
|
24
|
+
"websocket",
|
|
25
|
+
"ipc",
|
|
26
|
+
"mcp_server",
|
|
27
|
+
"install_tools",
|
|
28
|
+
"use_github",
|
|
29
|
+
"create_subagent",
|
|
30
|
+
"store_in_kb",
|
|
31
|
+
"system_prompt",
|
|
32
|
+
"tray",
|
|
33
|
+
"ambient",
|
|
34
|
+
"agentcore_config",
|
|
35
|
+
"agentcore_invoke",
|
|
36
|
+
"agentcore_logs",
|
|
37
|
+
"agentcore_agents",
|
|
38
|
+
]
|
|
39
|
+
except ImportError:
|
|
40
|
+
__all__ = [
|
|
41
|
+
"tcp",
|
|
42
|
+
"websocket",
|
|
43
|
+
"ipc",
|
|
44
|
+
"mcp_server",
|
|
45
|
+
"install_tools",
|
|
46
|
+
"use_github",
|
|
47
|
+
"create_subagent",
|
|
48
|
+
"store_in_kb",
|
|
49
|
+
"system_prompt",
|
|
50
|
+
"tray",
|
|
51
|
+
"ambient",
|
|
52
|
+
]
|
devduck/tools/_tray_app.py
CHANGED
|
@@ -83,6 +83,14 @@ class DevDuckTray(rumps.App):
|
|
|
83
83
|
"""Build menu with server controls and agent capabilities"""
|
|
84
84
|
self.menu.clear()
|
|
85
85
|
|
|
86
|
+
# Test Button - First item for easy testing
|
|
87
|
+
self.menu.add(
|
|
88
|
+
rumps.MenuItem(
|
|
89
|
+
"🧪 Test Agent", callback=self._create_callback("what time is it?")
|
|
90
|
+
)
|
|
91
|
+
)
|
|
92
|
+
self.menu.add(rumps.separator)
|
|
93
|
+
|
|
86
94
|
# Active Streams Section
|
|
87
95
|
if self.active_streams:
|
|
88
96
|
self.menu.add(rumps.MenuItem("🌊 Active Streams", callback=None))
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
"""AgentCore Agents Tool - List and manage deployed DevDuck agents."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from typing import Any, Dict, Optional
|
|
5
|
+
from strands import tool
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@tool
|
|
9
|
+
def agentcore_agents(
|
|
10
|
+
action: str = "list",
|
|
11
|
+
agent_id: Optional[str] = None,
|
|
12
|
+
agent_name: Optional[str] = None,
|
|
13
|
+
max_results: int = 100,
|
|
14
|
+
region: str = "us-west-2",
|
|
15
|
+
) -> Dict[str, Any]:
|
|
16
|
+
"""Manage and discover Bedrock AgentCore agent runtimes.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
action: Agent operation (list, get, find_by_name)
|
|
20
|
+
agent_id: Agent runtime ID (for "get" action)
|
|
21
|
+
agent_name: Agent name to search (for "find_by_name")
|
|
22
|
+
max_results: Max results for list (default: 100)
|
|
23
|
+
region: AWS region (default: us-west-2)
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
Dict with status and agent information
|
|
27
|
+
|
|
28
|
+
Examples:
|
|
29
|
+
# List all agents
|
|
30
|
+
agentcore_agents(action="list")
|
|
31
|
+
|
|
32
|
+
# Get specific agent
|
|
33
|
+
agentcore_agents(action="get", agent_id="devduck-UrvQvkH6H7")
|
|
34
|
+
|
|
35
|
+
# Find by name
|
|
36
|
+
agentcore_agents(action="find_by_name", agent_name="devduck")
|
|
37
|
+
"""
|
|
38
|
+
try:
|
|
39
|
+
import boto3
|
|
40
|
+
from botocore.exceptions import ClientError
|
|
41
|
+
|
|
42
|
+
client = boto3.client("bedrock-agentcore-control", region_name=region)
|
|
43
|
+
|
|
44
|
+
if action == "list":
|
|
45
|
+
all_agents = []
|
|
46
|
+
next_token = None
|
|
47
|
+
|
|
48
|
+
while True:
|
|
49
|
+
params = {"maxResults": min(max_results - len(all_agents), 100)}
|
|
50
|
+
if next_token:
|
|
51
|
+
params["nextToken"] = next_token
|
|
52
|
+
|
|
53
|
+
response = client.list_agent_runtimes(**params)
|
|
54
|
+
agents = response.get("agentRuntimes", [])
|
|
55
|
+
all_agents.extend(agents)
|
|
56
|
+
|
|
57
|
+
if len(all_agents) >= max_results:
|
|
58
|
+
all_agents = all_agents[:max_results]
|
|
59
|
+
break
|
|
60
|
+
|
|
61
|
+
next_token = response.get("nextToken")
|
|
62
|
+
if not next_token:
|
|
63
|
+
break
|
|
64
|
+
|
|
65
|
+
# Format output
|
|
66
|
+
agent_list = []
|
|
67
|
+
for agent in all_agents:
|
|
68
|
+
agent_list.append(
|
|
69
|
+
{
|
|
70
|
+
"name": agent.get("agentRuntimeName"),
|
|
71
|
+
"id": agent.get("agentRuntimeId"),
|
|
72
|
+
"arn": agent.get("agentRuntimeArn"),
|
|
73
|
+
"created": str(agent.get("createdAt")),
|
|
74
|
+
"updated": str(agent.get("lastUpdatedAt")),
|
|
75
|
+
}
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
"status": "success",
|
|
80
|
+
"content": [
|
|
81
|
+
{"text": f"**Found {len(agent_list)} agents:**\n"},
|
|
82
|
+
{"text": json.dumps(agent_list, indent=2)},
|
|
83
|
+
],
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
elif action == "get":
|
|
87
|
+
if not agent_id:
|
|
88
|
+
return {
|
|
89
|
+
"status": "error",
|
|
90
|
+
"content": [{"text": "agent_id required for get action"}],
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
response = client.get_agent_runtime(agentRuntimeId=agent_id)
|
|
94
|
+
|
|
95
|
+
agent_info = {
|
|
96
|
+
"id": response.get("agentRuntimeId"),
|
|
97
|
+
"arn": response.get("agentRuntimeArn"),
|
|
98
|
+
"name": response.get("agentRuntimeName"),
|
|
99
|
+
"status": response.get("status"),
|
|
100
|
+
"roleArn": response.get("roleArn"),
|
|
101
|
+
"created": str(response.get("createdAt")),
|
|
102
|
+
"updated": str(response.get("lastUpdatedAt")),
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
# Add container info
|
|
106
|
+
if "agentRuntimeArtifact" in response:
|
|
107
|
+
container = response["agentRuntimeArtifact"].get(
|
|
108
|
+
"containerConfiguration", {}
|
|
109
|
+
)
|
|
110
|
+
if container:
|
|
111
|
+
agent_info["containerUri"] = container.get("containerUri")
|
|
112
|
+
|
|
113
|
+
# Add network info
|
|
114
|
+
if "networkConfiguration" in response:
|
|
115
|
+
agent_info["networkMode"] = response["networkConfiguration"].get(
|
|
116
|
+
"networkMode"
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
"status": "success",
|
|
121
|
+
"content": [
|
|
122
|
+
{"text": "**Agent Details:**\n"},
|
|
123
|
+
{"text": json.dumps(agent_info, indent=2)},
|
|
124
|
+
],
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
elif action == "find_by_name":
|
|
128
|
+
if not agent_name:
|
|
129
|
+
return {
|
|
130
|
+
"status": "error",
|
|
131
|
+
"content": [
|
|
132
|
+
{"text": "agent_name required for find_by_name action"}
|
|
133
|
+
],
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
# List and search
|
|
137
|
+
all_agents = []
|
|
138
|
+
next_token = None
|
|
139
|
+
|
|
140
|
+
while True:
|
|
141
|
+
params = {"maxResults": 100}
|
|
142
|
+
if next_token:
|
|
143
|
+
params["nextToken"] = next_token
|
|
144
|
+
|
|
145
|
+
response = client.list_agent_runtimes(**params)
|
|
146
|
+
agents = response.get("agentRuntimes", [])
|
|
147
|
+
all_agents.extend(agents)
|
|
148
|
+
|
|
149
|
+
next_token = response.get("nextToken")
|
|
150
|
+
if not next_token:
|
|
151
|
+
break
|
|
152
|
+
|
|
153
|
+
# Find match
|
|
154
|
+
matching = None
|
|
155
|
+
for agent in all_agents:
|
|
156
|
+
if agent.get("agentRuntimeName") == agent_name:
|
|
157
|
+
matching = agent
|
|
158
|
+
break
|
|
159
|
+
|
|
160
|
+
if matching:
|
|
161
|
+
return {
|
|
162
|
+
"status": "success",
|
|
163
|
+
"content": [
|
|
164
|
+
{"text": f"✅ **Found: {agent_name}**\n"},
|
|
165
|
+
{"text": json.dumps(matching, indent=2, default=str)},
|
|
166
|
+
],
|
|
167
|
+
}
|
|
168
|
+
else:
|
|
169
|
+
return {
|
|
170
|
+
"status": "error",
|
|
171
|
+
"content": [{"text": f"Agent not found: {agent_name}"}],
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
else:
|
|
175
|
+
return {
|
|
176
|
+
"status": "error",
|
|
177
|
+
"content": [
|
|
178
|
+
{
|
|
179
|
+
"text": f"Unknown action: {action}. Valid: list, get, find_by_name"
|
|
180
|
+
}
|
|
181
|
+
],
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
except ClientError as e:
|
|
185
|
+
error_code = e.response.get("Error", {}).get("Code", "Unknown")
|
|
186
|
+
error_message = e.response.get("Error", {}).get("Message", str(e))
|
|
187
|
+
|
|
188
|
+
return {
|
|
189
|
+
"status": "error",
|
|
190
|
+
"content": [
|
|
191
|
+
{"text": f"**AWS Error ({error_code}):** {error_message}"},
|
|
192
|
+
{"text": f"**Region:** {region}"},
|
|
193
|
+
],
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
except Exception as e:
|
|
197
|
+
return {"status": "error", "content": [{"text": f"Error: {str(e)}"}]}
|