devduck 0.1.0__py3-none-any.whl → 0.1.1766644714__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.

Files changed (37) hide show
  1. devduck/__init__.py +1439 -483
  2. devduck/__main__.py +7 -0
  3. devduck/_version.py +34 -0
  4. devduck/agentcore_handler.py +76 -0
  5. devduck/test_redduck.py +0 -1
  6. devduck/tools/__init__.py +47 -0
  7. devduck/tools/_ambient_input.py +423 -0
  8. devduck/tools/_tray_app.py +530 -0
  9. devduck/tools/agentcore_agents.py +197 -0
  10. devduck/tools/agentcore_config.py +441 -0
  11. devduck/tools/agentcore_invoke.py +423 -0
  12. devduck/tools/agentcore_logs.py +320 -0
  13. devduck/tools/ambient.py +157 -0
  14. devduck/tools/create_subagent.py +659 -0
  15. devduck/tools/fetch_github_tool.py +201 -0
  16. devduck/tools/install_tools.py +409 -0
  17. devduck/tools/ipc.py +546 -0
  18. devduck/tools/mcp_server.py +600 -0
  19. devduck/tools/scraper.py +935 -0
  20. devduck/tools/speech_to_speech.py +850 -0
  21. devduck/tools/state_manager.py +292 -0
  22. devduck/tools/store_in_kb.py +187 -0
  23. devduck/tools/system_prompt.py +608 -0
  24. devduck/tools/tcp.py +263 -94
  25. devduck/tools/tray.py +247 -0
  26. devduck/tools/use_github.py +438 -0
  27. devduck/tools/websocket.py +498 -0
  28. devduck-0.1.1766644714.dist-info/METADATA +717 -0
  29. devduck-0.1.1766644714.dist-info/RECORD +33 -0
  30. {devduck-0.1.0.dist-info → devduck-0.1.1766644714.dist-info}/entry_points.txt +1 -0
  31. devduck-0.1.1766644714.dist-info/licenses/LICENSE +201 -0
  32. devduck/install.sh +0 -42
  33. devduck-0.1.0.dist-info/METADATA +0 -106
  34. devduck-0.1.0.dist-info/RECORD +0 -11
  35. devduck-0.1.0.dist-info/licenses/LICENSE +0 -21
  36. {devduck-0.1.0.dist-info → devduck-0.1.1766644714.dist-info}/WHEEL +0 -0
  37. {devduck-0.1.0.dist-info → devduck-0.1.1766644714.dist-info}/top_level.txt +0 -0
@@ -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)}"}]}
@@ -0,0 +1,441 @@
1
+ """AgentCore Configuration and Launch Tool"""
2
+
3
+ import os
4
+ import subprocess
5
+ from pathlib import Path
6
+ from typing import Dict, Any
7
+ from strands import tool
8
+
9
+
10
+ @tool
11
+ def agentcore_config(
12
+ action: str,
13
+ agent_name: str = "devduck",
14
+ handler_path: str = None,
15
+ region: str = "us-west-2",
16
+ model_id: str = "us.anthropic.claude-sonnet-4-5-20250929-v1:0",
17
+ max_tokens: int = 60000,
18
+ tools: str = None,
19
+ idle_timeout: int = 900,
20
+ max_lifetime: int = 28800,
21
+ auto_launch: bool = False,
22
+ ) -> Dict[str, Any]:
23
+ """
24
+ Configure and launch DevDuck agents on Bedrock AgentCore.
25
+
26
+ **Hardcoded for DevDuck:**
27
+ - deployment_type: "direct_code_deploy" (fast, no Docker)
28
+ - protocol: "HTTP" (standard API)
29
+ - runtime: "PYTHON_3_13" (latest)
30
+
31
+ **Quick Start:**
32
+ ```python
33
+ # Configure and launch in one step
34
+ agentcore_config(
35
+ action="configure",
36
+ agent_name="my-agent",
37
+ auto_launch=True
38
+ )
39
+ ```
40
+
41
+ Args:
42
+ action: Action to perform (generate, configure, launch, status)
43
+ agent_name: Name for the agent (default: devduck)
44
+ - Hyphens auto-converted to underscores
45
+ - Example: "test-agent" → "test_agent"
46
+ handler_path: Path to handler.py (default: auto-copy from devduck)
47
+ region: AWS region (default: us-west-2)
48
+ model_id: Bedrock model ID (default: Claude Sonnet 4.5)
49
+ max_tokens: Max tokens to generate (default: 60000)
50
+ tools: Tool configuration (package:tool1,tool2)
51
+ Example: "strands_tools:shell,editor,file_read"
52
+ idle_timeout: Idle timeout in seconds (default: 900)
53
+ max_lifetime: Max lifetime in seconds (default: 28800)
54
+ auto_launch: Auto launch after configure (default: False)
55
+
56
+ Returns:
57
+ Dict with status and instructions
58
+
59
+ Examples:
60
+ # Configure and launch
61
+ agentcore_config(
62
+ action="configure",
63
+ agent_name="my-agent",
64
+ tools="strands_tools:shell,editor,file_read",
65
+ auto_launch=True
66
+ )
67
+
68
+ # Check status
69
+ agentcore_config(action="status", agent_name="my-agent")
70
+
71
+ # Generate commands only
72
+ agentcore_config(action="generate", agent_name="my-agent")
73
+ """
74
+
75
+ # Hardcoded values for DevDuck deployment
76
+ protocol = "HTTP"
77
+ deployment_type = "direct_code_deploy"
78
+ runtime = "PYTHON_3_13"
79
+
80
+ # Validate agent name
81
+ import re
82
+
83
+ if not re.match(r"^[a-zA-Z][a-zA-Z0-9_]{0,47}$", agent_name):
84
+ # Auto-fix: replace hyphens with underscores
85
+ fixed_name = agent_name.replace("-", "_")
86
+ if not re.match(r"^[a-zA-Z][a-zA-Z0-9_]{0,47}$", fixed_name):
87
+ return {
88
+ "status": "error",
89
+ "content": [
90
+ {
91
+ "text": f"Invalid agent name: {agent_name}\nMust start with letter, contain only letters/numbers/underscores, 1-48 chars"
92
+ }
93
+ ],
94
+ }
95
+ agent_name = fixed_name
96
+
97
+ # Auto-determine handler path
98
+ if not handler_path:
99
+ try:
100
+ import devduck
101
+
102
+ devduck_dir = Path(devduck.__file__).parent
103
+ source_handler = devduck_dir / "agentcore_handler.py"
104
+
105
+ if source_handler.exists():
106
+ # Copy to current directory with agent-specific name
107
+ local_handler = Path.cwd() / "agentcore_handler.py"
108
+ import shutil
109
+
110
+ shutil.copy(str(source_handler), str(local_handler))
111
+ handler_path = "./agentcore_handler.py"
112
+
113
+ # Create requirements.txt with devduck if it doesn't exist
114
+ requirements_path = Path.cwd() / "requirements.txt"
115
+ if not requirements_path.exists():
116
+ with open(requirements_path, "w") as f:
117
+ f.write("devduck\n")
118
+ else:
119
+ return {
120
+ "status": "error",
121
+ "content": [{"text": f"Handler not found: {source_handler}"}],
122
+ }
123
+ except Exception as e:
124
+ return {
125
+ "status": "error",
126
+ "content": [{"text": f"Failed to locate handler: {e}"}],
127
+ }
128
+ else:
129
+ # Ensure handler_path is local
130
+ handler_file = Path(handler_path)
131
+ if not handler_file.is_absolute():
132
+ # Already relative, make sure it starts with ./
133
+ if not handler_path.startswith("./"):
134
+ handler_path = f"./{handler_path}"
135
+ else:
136
+ # Absolute path - copy to current directory
137
+ local_handler = Path.cwd() / handler_file.name
138
+ import shutil
139
+
140
+ shutil.copy(str(handler_file), str(local_handler))
141
+ handler_path = f"./{handler_file.name}"
142
+
143
+ if action == "generate":
144
+ # Generate configuration commands
145
+ commands = f"""
146
+ # DevDuck AgentCore Configuration for: {agent_name}
147
+
148
+ # Step 1: Configure
149
+ agentcore configure \\
150
+ -e {handler_path} \\
151
+ -n {agent_name} \\
152
+ -r {region} \\
153
+ -p {protocol} \\
154
+ -dt {deployment_type} \\
155
+ -rt {runtime} \\
156
+ --idle-timeout {idle_timeout} \\
157
+ --max-lifetime {max_lifetime}
158
+
159
+ # Step 2: Launch
160
+ agentcore launch -a {agent_name} --auto-update-on-conflict
161
+
162
+ # Step 3: Check Status
163
+ agentcore status -a {agent_name}
164
+
165
+ # Step 4: Invoke
166
+ agentcore invoke {agent_name} "What are your capabilities?"
167
+
168
+ # Python Invocation:
169
+ from devduck import devduck
170
+ devduck.agent.tool.agentcore_invoke(
171
+ prompt="test query",
172
+ agent_name="{agent_name}"
173
+ )
174
+
175
+ # Environment Variables for Handler:
176
+ export MODEL_PROVIDER=bedrock
177
+ export STRANDS_PROVIDER=bedrock
178
+ export STRANDS_MODEL_ID={model_id}
179
+ export STRANDS_MAX_TOKENS={max_tokens}
180
+ export AWS_REGION={region}
181
+ export DEVDUCK_AUTO_START_SERVERS=false
182
+ """
183
+ if tools:
184
+ commands += f"export DEVDUCK_TOOLS={tools}\n"
185
+ else:
186
+ commands += "# export DEVDUCK_TOOLS=strands_tools:shell,editor,file_read # Limit tools for sub-agents\n"
187
+
188
+ return {
189
+ "status": "success",
190
+ "content": [{"text": f"Generated configuration:\n{commands}"}],
191
+ }
192
+
193
+ elif action == "configure":
194
+ # Run agentcore configure command with automated inputs
195
+ try:
196
+ # Set environment variables for handler
197
+ env = os.environ.copy()
198
+ env["DEVDUCK_AUTO_START_SERVERS"] = "false"
199
+ if tools:
200
+ env["DEVDUCK_TOOLS"] = tools
201
+
202
+ cmd = [
203
+ "agentcore",
204
+ "configure",
205
+ "-e",
206
+ handler_path,
207
+ "-n",
208
+ agent_name,
209
+ "-r",
210
+ region,
211
+ "-p",
212
+ protocol,
213
+ "-dt",
214
+ deployment_type,
215
+ "-rt",
216
+ runtime,
217
+ "--idle-timeout",
218
+ str(idle_timeout),
219
+ "--max-lifetime",
220
+ str(max_lifetime),
221
+ ]
222
+
223
+ # Automated inputs for interactive prompts:
224
+ # 1. Requirements file path (Enter)
225
+ # 2. Execution role (Enter for auto-create)
226
+ # 3. S3 bucket (Enter for auto-create)
227
+ # 4. OAuth config (no/Enter)
228
+ # 5. Request headers (no/Enter)
229
+ # 6. Memory selection (s to skip)
230
+ automated_inputs = "\n\n\n\n\ns\n"
231
+
232
+ process = subprocess.Popen(
233
+ cmd,
234
+ stdin=subprocess.PIPE,
235
+ stdout=subprocess.PIPE,
236
+ stderr=subprocess.PIPE,
237
+ text=True,
238
+ env=env,
239
+ )
240
+
241
+ stdout, stderr = process.communicate(input=automated_inputs, timeout=300)
242
+
243
+ if process.returncode != 0:
244
+ return {
245
+ "status": "error",
246
+ "content": [{"text": f"Configure failed:\n{stderr}\n{stdout}"}],
247
+ }
248
+
249
+ output = f"✅ Configured: {agent_name}\n{stdout}"
250
+
251
+ # Auto-launch if requested
252
+ agent_arn = None
253
+ agent_id = None
254
+
255
+ if auto_launch:
256
+ launch_cmd = [
257
+ "agentcore",
258
+ "launch",
259
+ "-a",
260
+ agent_name,
261
+ "--auto-update-on-conflict",
262
+ ]
263
+
264
+ output += f"\n\n🚀 Launching {agent_name}...\n"
265
+ output += "=" * 50 + "\n"
266
+
267
+ # Stream output to BOTH terminal AND agent
268
+ import sys
269
+ import re
270
+
271
+ launch_process = subprocess.Popen(
272
+ launch_cmd,
273
+ stdin=subprocess.PIPE,
274
+ stdout=subprocess.PIPE,
275
+ stderr=subprocess.STDOUT,
276
+ text=True,
277
+ bufsize=1,
278
+ )
279
+
280
+ # Send automated inputs
281
+ launch_inputs = "\n\n\n"
282
+ launch_process.stdin.write(launch_inputs)
283
+ launch_process.stdin.close()
284
+
285
+ # Stream output line by line
286
+ launch_output = []
287
+ try:
288
+ for line in launch_process.stdout:
289
+ print(line, end="", flush=True) # Terminal
290
+ launch_output.append(line) # Agent
291
+
292
+ launch_process.wait(timeout=600)
293
+
294
+ full_output = "".join(launch_output)
295
+ if launch_process.returncode != 0:
296
+ output += f"\n❌ Launch failed with code {launch_process.returncode}\n{full_output}"
297
+ else:
298
+ output += f"\n✅ Launched: {agent_name}\n{full_output}"
299
+
300
+ # Extract ARN after streaming is complete
301
+ arn_match = re.search(
302
+ r"arn:aws:bedrock-agentcore:[^:]+:[^:]+:runtime/([^\s\n]+)",
303
+ full_output,
304
+ )
305
+ if arn_match:
306
+ agent_arn = arn_match.group(0)
307
+ agent_id = arn_match.group(1)
308
+ except subprocess.TimeoutExpired:
309
+ launch_process.kill()
310
+ output += f"\n❌ Launch timed out"
311
+
312
+ # Return structured response with agent_id for direct invocation
313
+ result = {"status": "success", "content": [{"text": output}]}
314
+
315
+ # Add structured metadata in content for easy access
316
+ if agent_arn and agent_id:
317
+ result["content"].append({"text": f"\n📋 **Agent ARN:** {agent_arn}"})
318
+ result["content"].append({"text": f"🆔 **Agent ID:** {agent_id}"})
319
+ result["content"].append(
320
+ {
321
+ "text": f"\n💡 **Invoke directly:** agentcore_invoke(agent_id='{agent_id}', prompt='your query')"
322
+ }
323
+ )
324
+
325
+ return result
326
+
327
+ except FileNotFoundError:
328
+ return {
329
+ "status": "error",
330
+ "content": [
331
+ {
332
+ "text": "agentcore CLI not found. Install: pip install bedrock-agentcore-starter-toolkit"
333
+ }
334
+ ],
335
+ }
336
+ except subprocess.TimeoutExpired:
337
+ return {
338
+ "status": "error",
339
+ "content": [{"text": f"Configuration timed out for {agent_name}"}],
340
+ }
341
+ except Exception as e:
342
+ return {
343
+ "status": "error",
344
+ "content": [{"text": f"Configuration error: {e}"}],
345
+ }
346
+
347
+ elif action == "launch":
348
+ # Run agentcore launch command with streaming output
349
+ try:
350
+ import sys
351
+ import re
352
+
353
+ cmd = ["agentcore", "launch", "-a", agent_name, "--auto-update-on-conflict"]
354
+
355
+ print(f"🚀 Launching {agent_name}...")
356
+ print("=" * 50)
357
+
358
+ # Stream to BOTH terminal AND agent
359
+ process = subprocess.Popen(
360
+ cmd,
361
+ stdout=subprocess.PIPE,
362
+ stderr=subprocess.STDOUT,
363
+ text=True,
364
+ bufsize=1,
365
+ )
366
+
367
+ # Capture output
368
+ output_lines = []
369
+ for line in process.stdout:
370
+ print(line, end="", flush=True) # Terminal
371
+ output_lines.append(line) # Agent
372
+
373
+ process.wait()
374
+ full_output = "".join(output_lines)
375
+
376
+ # Extract ARN after streaming is complete
377
+ agent_arn = None
378
+ agent_id = None
379
+ arn_match = re.search(
380
+ r"arn:aws:bedrock-agentcore:[^:]+:[^:]+:runtime/([^\s\n]+)", full_output
381
+ )
382
+ if arn_match:
383
+ agent_arn = arn_match.group(0)
384
+ agent_id = arn_match.group(1)
385
+
386
+ if process.returncode != 0:
387
+ return {
388
+ "status": "error",
389
+ "content": [
390
+ {
391
+ "text": f"❌ Launch failed with code {process.returncode}\n{full_output}"
392
+ }
393
+ ],
394
+ }
395
+
396
+ result = {
397
+ "status": "success",
398
+ "content": [{"text": f"✅ Launched: {agent_name}\n{full_output}"}],
399
+ }
400
+
401
+ # Add structured metadata in content for easy access
402
+ if agent_arn and agent_id:
403
+ result["content"].append({"text": f"\n📋 **Agent ARN:** {agent_arn}"})
404
+ result["content"].append({"text": f"🆔 **Agent ID:** {agent_id}"})
405
+ result["content"].append(
406
+ {
407
+ "text": f"\n💡 **Invoke directly:** agentcore_invoke(agent_id='{agent_id}', prompt='your query')"
408
+ }
409
+ )
410
+
411
+ return result
412
+
413
+ except Exception as e:
414
+ return {"status": "error", "content": [{"text": f"Launch error: {e}"}]}
415
+
416
+ elif action == "status":
417
+ # Check agent status
418
+ try:
419
+ cmd = ["agentcore", "status", "-a", agent_name]
420
+ result = subprocess.run(cmd, capture_output=True, text=True)
421
+
422
+ return {
423
+ "status": "success",
424
+ "content": [{"text": f"Status for {agent_name}:\n{result.stdout}"}],
425
+ }
426
+
427
+ except Exception as e:
428
+ return {
429
+ "status": "error",
430
+ "content": [{"text": f"Status check error: {e}"}],
431
+ }
432
+
433
+ else:
434
+ return {
435
+ "status": "error",
436
+ "content": [
437
+ {
438
+ "text": f"Unknown action: {action}. Use: generate, configure, launch, status"
439
+ }
440
+ ],
441
+ }