daita-agents 0.2.0__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.
- daita/__init__.py +216 -0
- daita/agents/__init__.py +33 -0
- daita/agents/base.py +743 -0
- daita/agents/substrate.py +1141 -0
- daita/cli/__init__.py +145 -0
- daita/cli/__main__.py +7 -0
- daita/cli/ascii_art.py +44 -0
- daita/cli/core/__init__.py +0 -0
- daita/cli/core/create.py +254 -0
- daita/cli/core/deploy.py +473 -0
- daita/cli/core/deployments.py +309 -0
- daita/cli/core/import_detector.py +219 -0
- daita/cli/core/init.py +481 -0
- daita/cli/core/logs.py +239 -0
- daita/cli/core/managed_deploy.py +709 -0
- daita/cli/core/run.py +648 -0
- daita/cli/core/status.py +421 -0
- daita/cli/core/test.py +239 -0
- daita/cli/core/webhooks.py +172 -0
- daita/cli/main.py +588 -0
- daita/cli/utils.py +541 -0
- daita/config/__init__.py +62 -0
- daita/config/base.py +159 -0
- daita/config/settings.py +184 -0
- daita/core/__init__.py +262 -0
- daita/core/decision_tracing.py +701 -0
- daita/core/exceptions.py +480 -0
- daita/core/focus.py +251 -0
- daita/core/interfaces.py +76 -0
- daita/core/plugin_tracing.py +550 -0
- daita/core/relay.py +779 -0
- daita/core/reliability.py +381 -0
- daita/core/scaling.py +459 -0
- daita/core/tools.py +554 -0
- daita/core/tracing.py +770 -0
- daita/core/workflow.py +1144 -0
- daita/display/__init__.py +1 -0
- daita/display/console.py +160 -0
- daita/execution/__init__.py +58 -0
- daita/execution/client.py +856 -0
- daita/execution/exceptions.py +92 -0
- daita/execution/models.py +317 -0
- daita/llm/__init__.py +60 -0
- daita/llm/anthropic.py +291 -0
- daita/llm/base.py +530 -0
- daita/llm/factory.py +101 -0
- daita/llm/gemini.py +355 -0
- daita/llm/grok.py +219 -0
- daita/llm/mock.py +172 -0
- daita/llm/openai.py +220 -0
- daita/plugins/__init__.py +141 -0
- daita/plugins/base.py +37 -0
- daita/plugins/base_db.py +167 -0
- daita/plugins/elasticsearch.py +849 -0
- daita/plugins/mcp.py +481 -0
- daita/plugins/mongodb.py +520 -0
- daita/plugins/mysql.py +362 -0
- daita/plugins/postgresql.py +342 -0
- daita/plugins/redis_messaging.py +500 -0
- daita/plugins/rest.py +537 -0
- daita/plugins/s3.py +770 -0
- daita/plugins/slack.py +729 -0
- daita/utils/__init__.py +18 -0
- daita_agents-0.2.0.dist-info/METADATA +409 -0
- daita_agents-0.2.0.dist-info/RECORD +69 -0
- daita_agents-0.2.0.dist-info/WHEEL +5 -0
- daita_agents-0.2.0.dist-info/entry_points.txt +2 -0
- daita_agents-0.2.0.dist-info/licenses/LICENSE +56 -0
- daita_agents-0.2.0.dist-info/top_level.txt +1 -0
daita/cli/core/run.py
ADDED
|
@@ -0,0 +1,648 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Remote execution command for running agents and workflows in the cloud.
|
|
3
|
+
|
|
4
|
+
This allows users to execute their deployed agents/workflows remotely
|
|
5
|
+
from the command line, with real-time status updates and result streaming.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import asyncio
|
|
9
|
+
import json
|
|
10
|
+
import os
|
|
11
|
+
import sys
|
|
12
|
+
import time
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import Dict, Any, Optional
|
|
15
|
+
import aiohttp
|
|
16
|
+
import click
|
|
17
|
+
import yaml
|
|
18
|
+
from ..utils import find_project_root
|
|
19
|
+
|
|
20
|
+
async def run_remote_execution(
|
|
21
|
+
target_name: str,
|
|
22
|
+
target_type: str = "agent",
|
|
23
|
+
environment: str = "production",
|
|
24
|
+
data_file: Optional[str] = None,
|
|
25
|
+
data_json: Optional[str] = None,
|
|
26
|
+
task: str = "process",
|
|
27
|
+
follow: bool = False,
|
|
28
|
+
timeout: int = 300,
|
|
29
|
+
verbose: bool = False
|
|
30
|
+
):
|
|
31
|
+
"""
|
|
32
|
+
Execute an agent or workflow remotely in the cloud.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
target_name: Name of the agent or workflow to execute
|
|
36
|
+
target_type: "agent" or "workflow"
|
|
37
|
+
environment: Environment to execute in (production, staging)
|
|
38
|
+
data_file: Path to JSON file containing input data
|
|
39
|
+
data_json: JSON string containing input data
|
|
40
|
+
task: Task to execute (for agents only)
|
|
41
|
+
follow: Whether to follow execution progress
|
|
42
|
+
timeout: Execution timeout in seconds
|
|
43
|
+
verbose: Enable verbose output
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
# Load environment variables from .env file if present
|
|
47
|
+
try:
|
|
48
|
+
from dotenv import load_dotenv
|
|
49
|
+
load_dotenv()
|
|
50
|
+
except ImportError:
|
|
51
|
+
pass # dotenv is optional
|
|
52
|
+
|
|
53
|
+
# Get API credentials
|
|
54
|
+
api_key = os.getenv('DAITA_API_KEY')
|
|
55
|
+
api_base = os.getenv('DAITA_API_BASE') or os.getenv('DAITA_API_ENDPOINT') or 'https://ondk4sdyv0.execute-api.us-east-1.amazonaws.com'
|
|
56
|
+
|
|
57
|
+
if not api_key:
|
|
58
|
+
click.echo(" DAITA_API_KEY not found", err=True)
|
|
59
|
+
click.echo(" Get your API key from daita-tech.io", err=True)
|
|
60
|
+
click.echo(" Set it with: export DAITA_API_KEY='your-key-here'", err=True)
|
|
61
|
+
click.echo(" Or add it to your project's .env file", err=True)
|
|
62
|
+
return False
|
|
63
|
+
|
|
64
|
+
# Prepare input data
|
|
65
|
+
input_data = {}
|
|
66
|
+
if data_file:
|
|
67
|
+
try:
|
|
68
|
+
with open(data_file, 'r') as f:
|
|
69
|
+
input_data = json.load(f)
|
|
70
|
+
if verbose:
|
|
71
|
+
click.echo(f" Loaded data from {data_file}")
|
|
72
|
+
except Exception as e:
|
|
73
|
+
click.echo(f" Failed to load data file: {e}", err=True)
|
|
74
|
+
return False
|
|
75
|
+
elif data_json:
|
|
76
|
+
try:
|
|
77
|
+
input_data = json.loads(data_json)
|
|
78
|
+
except Exception as e:
|
|
79
|
+
click.echo(f" Invalid JSON data: {e}", err=True)
|
|
80
|
+
return False
|
|
81
|
+
|
|
82
|
+
# Prepare execution request
|
|
83
|
+
request_data = {
|
|
84
|
+
"data": input_data,
|
|
85
|
+
"environment": environment,
|
|
86
|
+
"timeout_seconds": timeout,
|
|
87
|
+
"execution_source": "cli",
|
|
88
|
+
"source_metadata": {
|
|
89
|
+
"cli_version": "1.0.0",
|
|
90
|
+
"command": f"daita run {target_name}",
|
|
91
|
+
"working_directory": str(Path.cwd())
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
# Validate agent exists and get file name for execution
|
|
96
|
+
if verbose:
|
|
97
|
+
click.echo(f" Looking up agent '{target_name}'...")
|
|
98
|
+
|
|
99
|
+
agent_info = validate_agent_exists(target_name, target_type)
|
|
100
|
+
if not agent_info:
|
|
101
|
+
return False
|
|
102
|
+
|
|
103
|
+
file_name = agent_info['file_name']
|
|
104
|
+
display_name = agent_info.get('display_name', file_name)
|
|
105
|
+
|
|
106
|
+
if verbose:
|
|
107
|
+
click.echo(f" Found agent: {file_name} → '{display_name}'")
|
|
108
|
+
click.echo(f" Executing with file name: '{file_name}'")
|
|
109
|
+
|
|
110
|
+
# Add target-specific fields using file name (API/Lambda expects this)
|
|
111
|
+
if target_type == "agent":
|
|
112
|
+
request_data["agent_name"] = file_name
|
|
113
|
+
request_data["task"] = task
|
|
114
|
+
else:
|
|
115
|
+
request_data["workflow_name"] = file_name
|
|
116
|
+
|
|
117
|
+
if verbose:
|
|
118
|
+
click.echo(f" Executing {target_type} '{target_name}' in {environment}")
|
|
119
|
+
if input_data:
|
|
120
|
+
click.echo(f" Input data: {len(str(input_data))} characters")
|
|
121
|
+
|
|
122
|
+
# Execute the request
|
|
123
|
+
headers = {
|
|
124
|
+
'Authorization': f'Bearer {api_key}',
|
|
125
|
+
'Content-Type': 'application/json',
|
|
126
|
+
'User-Agent': 'Daita-CLI/1.0.0'
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
try:
|
|
130
|
+
async with aiohttp.ClientSession() as session:
|
|
131
|
+
# Submit execution request
|
|
132
|
+
async with session.post(
|
|
133
|
+
f"{api_base}/api/v1/executions/execute",
|
|
134
|
+
headers=headers,
|
|
135
|
+
json=request_data
|
|
136
|
+
) as response:
|
|
137
|
+
|
|
138
|
+
if response.status != 200:
|
|
139
|
+
error_data = await response.json()
|
|
140
|
+
error_detail = error_data.get('detail', 'Unknown error')
|
|
141
|
+
click.echo(f" Execution failed: {error_detail}", err=True)
|
|
142
|
+
|
|
143
|
+
# Provide helpful guidance based on error type
|
|
144
|
+
if response.status == 404 and "No deployment found" in error_detail:
|
|
145
|
+
click.echo(f" Possible causes:", err=True)
|
|
146
|
+
click.echo(f" • Agent not deployed: daita push {environment}", err=True)
|
|
147
|
+
click.echo(f" • Wrong agent name (using: '{file_name}')", err=True)
|
|
148
|
+
click.echo(f" • Check deployed agents: daita status", err=True)
|
|
149
|
+
|
|
150
|
+
return False
|
|
151
|
+
|
|
152
|
+
result = await response.json()
|
|
153
|
+
execution_id = result['execution_id']
|
|
154
|
+
|
|
155
|
+
if verbose:
|
|
156
|
+
click.echo(f" Execution ID: {execution_id}")
|
|
157
|
+
|
|
158
|
+
# All executions are now asynchronous
|
|
159
|
+
if follow:
|
|
160
|
+
# Show progress while following
|
|
161
|
+
return await _follow_execution(session, api_base, headers, execution_id, verbose)
|
|
162
|
+
else:
|
|
163
|
+
# Poll once for immediate result, then return
|
|
164
|
+
return await _poll_for_result(session, api_base, headers, execution_id, verbose)
|
|
165
|
+
|
|
166
|
+
except aiohttp.ClientError as e:
|
|
167
|
+
click.echo(f" Network error: {e}", err=True)
|
|
168
|
+
return False
|
|
169
|
+
except Exception as e:
|
|
170
|
+
click.echo(f" Unexpected error: {e}", err=True)
|
|
171
|
+
return False
|
|
172
|
+
|
|
173
|
+
async def _follow_execution(
|
|
174
|
+
session: aiohttp.ClientSession,
|
|
175
|
+
api_base: str,
|
|
176
|
+
headers: Dict[str, str],
|
|
177
|
+
execution_id: str,
|
|
178
|
+
verbose: bool
|
|
179
|
+
) -> bool:
|
|
180
|
+
"""Follow an asynchronous execution until completion."""
|
|
181
|
+
|
|
182
|
+
click.echo(" Following execution progress...")
|
|
183
|
+
|
|
184
|
+
last_status = None
|
|
185
|
+
start_time = time.time()
|
|
186
|
+
|
|
187
|
+
try:
|
|
188
|
+
while True:
|
|
189
|
+
# Check execution status
|
|
190
|
+
async with session.get(
|
|
191
|
+
f"{api_base}/api/v1/executions/{execution_id}",
|
|
192
|
+
headers=headers
|
|
193
|
+
) as response:
|
|
194
|
+
|
|
195
|
+
if response.status != 200:
|
|
196
|
+
click.echo(" Failed to get execution status", err=True)
|
|
197
|
+
return False
|
|
198
|
+
|
|
199
|
+
result = await response.json()
|
|
200
|
+
status = result['status']
|
|
201
|
+
|
|
202
|
+
# Display status changes
|
|
203
|
+
if status != last_status:
|
|
204
|
+
elapsed = int(time.time() - start_time)
|
|
205
|
+
|
|
206
|
+
if status == 'running':
|
|
207
|
+
click.echo(f" Execution started (after {elapsed}s)")
|
|
208
|
+
elif status in ['completed', 'success']: # Handle both status values
|
|
209
|
+
click.echo(f" Execution completed (after {elapsed}s)")
|
|
210
|
+
_display_result(result, verbose)
|
|
211
|
+
return True
|
|
212
|
+
elif status in ['failed', 'error']: # Handle both status values
|
|
213
|
+
click.echo(f" Execution failed (after {elapsed}s)")
|
|
214
|
+
if result.get('error'):
|
|
215
|
+
click.echo(f" Error: {result['error']}")
|
|
216
|
+
return False
|
|
217
|
+
elif status == 'cancelled':
|
|
218
|
+
click.echo(f" Execution cancelled (after {elapsed}s)")
|
|
219
|
+
return False
|
|
220
|
+
|
|
221
|
+
last_status = status
|
|
222
|
+
|
|
223
|
+
# Break if execution is complete
|
|
224
|
+
if status in ['completed', 'success', 'failed', 'error', 'cancelled']:
|
|
225
|
+
break
|
|
226
|
+
|
|
227
|
+
# Wait before next check
|
|
228
|
+
await asyncio.sleep(2)
|
|
229
|
+
|
|
230
|
+
except KeyboardInterrupt:
|
|
231
|
+
click.echo("\n Stopped following execution (execution continues in background)")
|
|
232
|
+
click.echo(f" Check status with: daita logs --execution-id {execution_id}")
|
|
233
|
+
return False
|
|
234
|
+
|
|
235
|
+
async def _poll_for_result(
|
|
236
|
+
session: aiohttp.ClientSession,
|
|
237
|
+
api_base: str,
|
|
238
|
+
headers: Dict[str, str],
|
|
239
|
+
execution_id: str,
|
|
240
|
+
verbose: bool
|
|
241
|
+
) -> bool:
|
|
242
|
+
"""Poll for execution result without showing progress (for non-follow mode)."""
|
|
243
|
+
|
|
244
|
+
max_polls = 360 # 3 minutes max wait (0.5s intervals)
|
|
245
|
+
polls = 0
|
|
246
|
+
|
|
247
|
+
while polls < max_polls:
|
|
248
|
+
try:
|
|
249
|
+
# Check execution status
|
|
250
|
+
async with session.get(
|
|
251
|
+
f"{api_base}/api/v1/executions/{execution_id}",
|
|
252
|
+
headers=headers
|
|
253
|
+
) as response:
|
|
254
|
+
|
|
255
|
+
if response.status != 200:
|
|
256
|
+
click.echo("⚠️ Failed to get execution status", err=True)
|
|
257
|
+
return False
|
|
258
|
+
|
|
259
|
+
result = await response.json()
|
|
260
|
+
status = result['status']
|
|
261
|
+
|
|
262
|
+
# Return result if complete
|
|
263
|
+
if status in ['completed', 'success']: # Handle both status values
|
|
264
|
+
click.echo("✅ Execution completed")
|
|
265
|
+
_display_result(result, verbose)
|
|
266
|
+
return True
|
|
267
|
+
elif status in ['failed', 'error']: # Handle both status values
|
|
268
|
+
click.echo("❌ Execution failed")
|
|
269
|
+
if result.get('error'):
|
|
270
|
+
click.echo(f" Error: {result['error']}")
|
|
271
|
+
return False
|
|
272
|
+
elif status == 'cancelled':
|
|
273
|
+
click.echo("🚫 Execution cancelled")
|
|
274
|
+
return False
|
|
275
|
+
|
|
276
|
+
# Continue polling
|
|
277
|
+
await asyncio.sleep(0.5)
|
|
278
|
+
polls += 1
|
|
279
|
+
|
|
280
|
+
except Exception as e:
|
|
281
|
+
click.echo(f"⚠️ Error polling execution: {e}", err=True)
|
|
282
|
+
return False
|
|
283
|
+
|
|
284
|
+
# Timeout reached
|
|
285
|
+
click.echo("⏰ Execution still running (timeout reached)")
|
|
286
|
+
click.echo(f" Check status with: daita logs --execution-id {execution_id}")
|
|
287
|
+
click.echo(f" Follow progress with: daita run {execution_id} --follow")
|
|
288
|
+
return False
|
|
289
|
+
|
|
290
|
+
def _display_result(result: Dict[str, Any], verbose: bool):
|
|
291
|
+
"""Display execution results in a formatted way."""
|
|
292
|
+
|
|
293
|
+
# Display basic info
|
|
294
|
+
if result.get('duration_ms'):
|
|
295
|
+
duration = float(result['duration_ms']) # Convert string to number
|
|
296
|
+
if duration < 1000:
|
|
297
|
+
click.echo(f" Duration: {duration:.0f}ms")
|
|
298
|
+
elif duration < 60000:
|
|
299
|
+
click.echo(f" Duration: {duration/1000:.1f}s")
|
|
300
|
+
else:
|
|
301
|
+
click.echo(f" Duration: {duration/60000:.1f}m")
|
|
302
|
+
|
|
303
|
+
if result.get('memory_used_mb'):
|
|
304
|
+
click.echo(f" Memory: {result['memory_used_mb']:.1f}MB")
|
|
305
|
+
|
|
306
|
+
# Display result data
|
|
307
|
+
if result.get('result'):
|
|
308
|
+
click.echo("\n Result:")
|
|
309
|
+
if verbose:
|
|
310
|
+
# Pretty print full result
|
|
311
|
+
click.echo(json.dumps(result['result'], indent=2))
|
|
312
|
+
else:
|
|
313
|
+
# Display summary
|
|
314
|
+
result_data = result['result']
|
|
315
|
+
if isinstance(result_data, dict):
|
|
316
|
+
if 'status' in result_data:
|
|
317
|
+
click.echo(f" Status: {result_data['status']}")
|
|
318
|
+
if 'message' in result_data:
|
|
319
|
+
click.echo(f" Message: {result_data['message']}")
|
|
320
|
+
if 'insights' in result_data:
|
|
321
|
+
click.echo(" Contains: insights data")
|
|
322
|
+
if 'recommendations' in result_data:
|
|
323
|
+
click.echo(" Contains: recommendations")
|
|
324
|
+
|
|
325
|
+
# Show keys for complex objects
|
|
326
|
+
if len(result_data) > 3:
|
|
327
|
+
keys = list(result_data.keys())[:5]
|
|
328
|
+
if len(result_data) > 5:
|
|
329
|
+
keys.append("...")
|
|
330
|
+
click.echo(f" Keys: {', '.join(keys)}")
|
|
331
|
+
else:
|
|
332
|
+
click.echo(f" {result_data}")
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
async def list_remote_executions(
|
|
336
|
+
limit: int = 10,
|
|
337
|
+
status: Optional[str] = None,
|
|
338
|
+
target_type: Optional[str] = None,
|
|
339
|
+
environment: Optional[str] = None,
|
|
340
|
+
verbose: bool = False
|
|
341
|
+
):
|
|
342
|
+
"""List recent executions with filtering."""
|
|
343
|
+
|
|
344
|
+
api_key = os.getenv('DAITA_API_KEY')
|
|
345
|
+
api_base = os.getenv('DAITA_API_BASE') or os.getenv('DAITA_API_ENDPOINT') or 'https://ondk4sdyv0.execute-api.us-east-1.amazonaws.com'
|
|
346
|
+
|
|
347
|
+
if not api_key:
|
|
348
|
+
click.echo(" DAITA_API_KEY not found", err=True)
|
|
349
|
+
return False
|
|
350
|
+
|
|
351
|
+
# Build query parameters
|
|
352
|
+
params = {'limit': limit}
|
|
353
|
+
if status:
|
|
354
|
+
params['status'] = status
|
|
355
|
+
if target_type:
|
|
356
|
+
params['target_type'] = target_type
|
|
357
|
+
if environment:
|
|
358
|
+
params['environment'] = environment
|
|
359
|
+
|
|
360
|
+
headers = {
|
|
361
|
+
'Authorization': f'Bearer {api_key}',
|
|
362
|
+
'User-Agent': 'Daita-CLI/1.0.0'
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
try:
|
|
366
|
+
async with aiohttp.ClientSession() as session:
|
|
367
|
+
async with session.get(
|
|
368
|
+
f"{api_base}/api/v1/executions",
|
|
369
|
+
headers=headers,
|
|
370
|
+
params=params
|
|
371
|
+
) as response:
|
|
372
|
+
|
|
373
|
+
if response.status != 200:
|
|
374
|
+
error_data = await response.json()
|
|
375
|
+
click.echo(f" Failed to list executions: {error_data.get('detail', 'Unknown error')}", err=True)
|
|
376
|
+
return False
|
|
377
|
+
|
|
378
|
+
executions = await response.json()
|
|
379
|
+
|
|
380
|
+
if not executions:
|
|
381
|
+
click.echo(" No executions found")
|
|
382
|
+
return True
|
|
383
|
+
|
|
384
|
+
# Display executions
|
|
385
|
+
click.echo(f" Recent executions ({len(executions)}):")
|
|
386
|
+
click.echo()
|
|
387
|
+
|
|
388
|
+
for execution in executions:
|
|
389
|
+
# Status icon
|
|
390
|
+
status_icon = {
|
|
391
|
+
'completed': '',
|
|
392
|
+
'failed': '',
|
|
393
|
+
'running': '',
|
|
394
|
+
'queued': '',
|
|
395
|
+
'cancelled': ''
|
|
396
|
+
}.get(execution['status'], '❓')
|
|
397
|
+
|
|
398
|
+
# Format time
|
|
399
|
+
created_at = execution['created_at']
|
|
400
|
+
if 'T' in created_at:
|
|
401
|
+
time_str = created_at.split('T')[1].split('.')[0]
|
|
402
|
+
else:
|
|
403
|
+
time_str = created_at
|
|
404
|
+
|
|
405
|
+
# Format duration
|
|
406
|
+
duration_str = "N/A"
|
|
407
|
+
if execution.get('duration_ms'):
|
|
408
|
+
ms = execution['duration_ms']
|
|
409
|
+
if ms < 1000:
|
|
410
|
+
duration_str = f"{ms}ms"
|
|
411
|
+
elif ms < 60000:
|
|
412
|
+
duration_str = f"{ms/1000:.1f}s"
|
|
413
|
+
else:
|
|
414
|
+
duration_str = f"{ms/60000:.1f}m"
|
|
415
|
+
|
|
416
|
+
click.echo(f"{status_icon} {execution['target_name']} ({execution['target_type']})")
|
|
417
|
+
click.echo(f" {execution['execution_id'][:8]}... | {time_str} | {duration_str} | {execution['environment']}")
|
|
418
|
+
|
|
419
|
+
if verbose and execution.get('error'):
|
|
420
|
+
click.echo(f" Error: {execution['error']}")
|
|
421
|
+
|
|
422
|
+
click.echo()
|
|
423
|
+
|
|
424
|
+
return True
|
|
425
|
+
|
|
426
|
+
except Exception as e:
|
|
427
|
+
click.echo(f" Error: {e}", err=True)
|
|
428
|
+
return False
|
|
429
|
+
|
|
430
|
+
async def get_execution_logs(
|
|
431
|
+
execution_id: str,
|
|
432
|
+
follow: bool = False,
|
|
433
|
+
verbose: bool = False
|
|
434
|
+
):
|
|
435
|
+
"""Get logs for a specific execution."""
|
|
436
|
+
|
|
437
|
+
api_key = os.getenv('DAITA_API_KEY')
|
|
438
|
+
api_base = os.getenv('DAITA_API_BASE') or os.getenv('DAITA_API_ENDPOINT') or 'https://ondk4sdyv0.execute-api.us-east-1.amazonaws.com'
|
|
439
|
+
|
|
440
|
+
if not api_key:
|
|
441
|
+
click.echo(" DAITA_API_KEY not found", err=True)
|
|
442
|
+
return False
|
|
443
|
+
|
|
444
|
+
headers = {
|
|
445
|
+
'Authorization': f'Bearer {api_key}',
|
|
446
|
+
'User-Agent': 'Daita-CLI/1.0.0'
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
try:
|
|
450
|
+
async with aiohttp.ClientSession() as session:
|
|
451
|
+
if follow:
|
|
452
|
+
# Follow mode - continuously check status
|
|
453
|
+
await _follow_execution(session, api_base, headers, execution_id, verbose)
|
|
454
|
+
else:
|
|
455
|
+
# One-time status check
|
|
456
|
+
async with session.get(
|
|
457
|
+
f"{api_base}/api/v1/executions/{execution_id}",
|
|
458
|
+
headers=headers
|
|
459
|
+
) as response:
|
|
460
|
+
|
|
461
|
+
if response.status == 404:
|
|
462
|
+
click.echo(" Execution not found", err=True)
|
|
463
|
+
return False
|
|
464
|
+
elif response.status != 200:
|
|
465
|
+
error_data = await response.json()
|
|
466
|
+
click.echo(f" Failed to get execution: {error_data.get('detail', 'Unknown error')}", err=True)
|
|
467
|
+
return False
|
|
468
|
+
|
|
469
|
+
result = await response.json()
|
|
470
|
+
|
|
471
|
+
# Display execution info
|
|
472
|
+
click.echo(f" Execution: {result['execution_id']}")
|
|
473
|
+
click.echo(f" Target: {result['target_name']} ({result['target_type']})")
|
|
474
|
+
click.echo(f" Environment: {result['environment']}")
|
|
475
|
+
click.echo(f" Status: {result['status']}")
|
|
476
|
+
|
|
477
|
+
if result.get('created_at'):
|
|
478
|
+
click.echo(f" Created: {result['created_at']}")
|
|
479
|
+
|
|
480
|
+
_display_result(result, verbose)
|
|
481
|
+
|
|
482
|
+
return True
|
|
483
|
+
|
|
484
|
+
except Exception as e:
|
|
485
|
+
click.echo(f" Error: {e}", err=True)
|
|
486
|
+
return False
|
|
487
|
+
|
|
488
|
+
|
|
489
|
+
async def _resolve_agent_id(agent_name: str, environment: str, api_key: str, api_base: str) -> Optional[str]:
|
|
490
|
+
"""
|
|
491
|
+
Resolve agent name to deployed agent ID.
|
|
492
|
+
|
|
493
|
+
This function queries the deployments API to find the actual agent ID
|
|
494
|
+
that corresponds to the simple agent name provided by the user.
|
|
495
|
+
|
|
496
|
+
Args:
|
|
497
|
+
agent_name: Simple agent name (e.g., "sentiment_analyzer")
|
|
498
|
+
environment: Target environment (e.g., "staging", "production")
|
|
499
|
+
api_key: DAITA API key for authentication
|
|
500
|
+
api_base: API base URL
|
|
501
|
+
|
|
502
|
+
Returns:
|
|
503
|
+
Agent ID if found, None otherwise
|
|
504
|
+
"""
|
|
505
|
+
try:
|
|
506
|
+
# Get current project name to filter deployments
|
|
507
|
+
project_root = find_project_root()
|
|
508
|
+
project_name = None
|
|
509
|
+
if project_root:
|
|
510
|
+
try:
|
|
511
|
+
import yaml
|
|
512
|
+
config_file = project_root / 'daita-project.yaml'
|
|
513
|
+
if config_file.exists():
|
|
514
|
+
with open(config_file, 'r') as f:
|
|
515
|
+
config = yaml.safe_load(f)
|
|
516
|
+
project_name = config.get('name')
|
|
517
|
+
except:
|
|
518
|
+
pass
|
|
519
|
+
|
|
520
|
+
headers = {
|
|
521
|
+
'Authorization': f'Bearer {api_key}',
|
|
522
|
+
'Content-Type': 'application/json',
|
|
523
|
+
'User-Agent': 'Daita-CLI/1.0.0'
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
async with aiohttp.ClientSession() as session:
|
|
527
|
+
# Query deployments API to find matching agent
|
|
528
|
+
url = f"{api_base}/api/v1/deployments"
|
|
529
|
+
params = {
|
|
530
|
+
'environment': environment,
|
|
531
|
+
'status': 'active'
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
# Add project filter if available
|
|
535
|
+
if project_name:
|
|
536
|
+
params['project_name'] = project_name
|
|
537
|
+
|
|
538
|
+
async with session.get(url, headers=headers, params=params) as response:
|
|
539
|
+
if response.status == 200:
|
|
540
|
+
deployments = await response.json()
|
|
541
|
+
|
|
542
|
+
# Debug logging
|
|
543
|
+
click.echo(f" DEBUG: Found {len(deployments)} deployments for project '{project_name}' in {environment}", err=True)
|
|
544
|
+
|
|
545
|
+
# Search through deployments for matching agent
|
|
546
|
+
for deployment in deployments:
|
|
547
|
+
agents_config = deployment.get('agents_config', [])
|
|
548
|
+
for agent_config in agents_config:
|
|
549
|
+
# Extract agent file name from agent_id
|
|
550
|
+
# Format: {org_id}_{project_name}_{agent_file_name}
|
|
551
|
+
agent_id = agent_config.get('agent_id', '')
|
|
552
|
+
agent_file_name = ''
|
|
553
|
+
|
|
554
|
+
if agent_id and project_name:
|
|
555
|
+
# Try to extract agent name by removing org_id and project_name prefix
|
|
556
|
+
# Expected format: "1_{project_name}_{agent_name}" where project_name may contain hyphens
|
|
557
|
+
# Strategy: look for pattern that starts with "1_{project_name}_"
|
|
558
|
+
prefix = f"1_{project_name}_"
|
|
559
|
+
if agent_id.startswith(prefix):
|
|
560
|
+
# Extract everything after the prefix
|
|
561
|
+
agent_file_name = agent_id[len(prefix):]
|
|
562
|
+
|
|
563
|
+
# Match by multiple criteria:
|
|
564
|
+
display_name = agent_config.get('agent_name', '')
|
|
565
|
+
|
|
566
|
+
if (agent_name == agent_file_name or
|
|
567
|
+
agent_name == display_name or
|
|
568
|
+
agent_name.replace('_', ' ').title() == display_name or
|
|
569
|
+
agent_name.replace('_', '').lower() == display_name.replace(' ', '').lower()):
|
|
570
|
+
return agent_config.get('agent_id')
|
|
571
|
+
|
|
572
|
+
return None
|
|
573
|
+
else:
|
|
574
|
+
# If API call fails, return None to let execution try with original name
|
|
575
|
+
click.echo(f" DEBUG: Deployments API returned status {response.status}", err=True)
|
|
576
|
+
return None
|
|
577
|
+
|
|
578
|
+
except Exception:
|
|
579
|
+
# If resolution fails, return None to let execution try with original name
|
|
580
|
+
return None
|
|
581
|
+
|
|
582
|
+
|
|
583
|
+
def validate_agent_exists(target_name: str, target_type: str = "agent") -> Optional[dict]:
|
|
584
|
+
"""
|
|
585
|
+
Validate agent exists and return file name and display name.
|
|
586
|
+
|
|
587
|
+
Args:
|
|
588
|
+
target_name: Name of the agent/workflow (file name or display name)
|
|
589
|
+
target_type: "agent" or "workflow"
|
|
590
|
+
|
|
591
|
+
Returns:
|
|
592
|
+
Dict with 'file_name' and 'display_name' if found, None otherwise
|
|
593
|
+
"""
|
|
594
|
+
project_root = find_project_root()
|
|
595
|
+
if not project_root:
|
|
596
|
+
click.echo(" No daita-project.yaml found")
|
|
597
|
+
click.echo(" Run 'daita init' to create a project")
|
|
598
|
+
return None
|
|
599
|
+
|
|
600
|
+
config_file = project_root / 'daita-project.yaml'
|
|
601
|
+
if not config_file.exists():
|
|
602
|
+
click.echo(" No daita-project.yaml found")
|
|
603
|
+
click.echo(" Run 'daita init' to create a project")
|
|
604
|
+
return None
|
|
605
|
+
|
|
606
|
+
try:
|
|
607
|
+
with open(config_file, 'r') as f:
|
|
608
|
+
config = yaml.safe_load(f)
|
|
609
|
+
except Exception as e:
|
|
610
|
+
click.echo(f" Failed to read daita-project.yaml: {e}")
|
|
611
|
+
return None
|
|
612
|
+
|
|
613
|
+
if not config:
|
|
614
|
+
click.echo(" Invalid daita-project.yaml file")
|
|
615
|
+
return None
|
|
616
|
+
|
|
617
|
+
# Get the appropriate component list
|
|
618
|
+
component_key = 'agents' if target_type == 'agent' else 'workflows'
|
|
619
|
+
components = config.get(component_key, [])
|
|
620
|
+
|
|
621
|
+
# Find the component by name OR display_name
|
|
622
|
+
component = next((c for c in components if c.get('name') == target_name), None)
|
|
623
|
+
|
|
624
|
+
# If not found by name, try finding by display_name
|
|
625
|
+
if not component:
|
|
626
|
+
component = next((c for c in components if c.get('display_name') == target_name), None)
|
|
627
|
+
|
|
628
|
+
if not component:
|
|
629
|
+
available_names = [c.get('name', 'unknown') for c in components]
|
|
630
|
+
click.echo(f" {target_type.title()} '{target_name}' not found in project")
|
|
631
|
+
if available_names:
|
|
632
|
+
click.echo(f" Available {component_key} (use file names): {', '.join(available_names)}")
|
|
633
|
+
else:
|
|
634
|
+
click.echo(f" No {component_key} found. Create one with: daita create {target_type}")
|
|
635
|
+
click.echo(" Use file names for execution (e.g., 'my_agent' not 'My Agent')")
|
|
636
|
+
return None
|
|
637
|
+
|
|
638
|
+
file_name = component.get('name')
|
|
639
|
+
display_name = component.get('display_name')
|
|
640
|
+
|
|
641
|
+
if not file_name:
|
|
642
|
+
click.echo(f" {target_type.title()} missing name in config")
|
|
643
|
+
return None
|
|
644
|
+
|
|
645
|
+
return {
|
|
646
|
+
'file_name': file_name,
|
|
647
|
+
'display_name': display_name or file_name
|
|
648
|
+
}
|