dtSpark 1.0.4__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.
- dtSpark/__init__.py +0 -0
- dtSpark/_description.txt +1 -0
- dtSpark/_full_name.txt +1 -0
- dtSpark/_licence.txt +21 -0
- dtSpark/_metadata.yaml +6 -0
- dtSpark/_name.txt +1 -0
- dtSpark/_version.txt +1 -0
- dtSpark/aws/__init__.py +7 -0
- dtSpark/aws/authentication.py +296 -0
- dtSpark/aws/bedrock.py +578 -0
- dtSpark/aws/costs.py +318 -0
- dtSpark/aws/pricing.py +580 -0
- dtSpark/cli_interface.py +2645 -0
- dtSpark/conversation_manager.py +3050 -0
- dtSpark/core/__init__.py +12 -0
- dtSpark/core/application.py +3355 -0
- dtSpark/core/context_compaction.py +735 -0
- dtSpark/daemon/__init__.py +104 -0
- dtSpark/daemon/__main__.py +10 -0
- dtSpark/daemon/action_monitor.py +213 -0
- dtSpark/daemon/daemon_app.py +730 -0
- dtSpark/daemon/daemon_manager.py +289 -0
- dtSpark/daemon/execution_coordinator.py +194 -0
- dtSpark/daemon/pid_file.py +169 -0
- dtSpark/database/__init__.py +482 -0
- dtSpark/database/autonomous_actions.py +1191 -0
- dtSpark/database/backends.py +329 -0
- dtSpark/database/connection.py +122 -0
- dtSpark/database/conversations.py +520 -0
- dtSpark/database/credential_prompt.py +218 -0
- dtSpark/database/files.py +205 -0
- dtSpark/database/mcp_ops.py +355 -0
- dtSpark/database/messages.py +161 -0
- dtSpark/database/schema.py +673 -0
- dtSpark/database/tool_permissions.py +186 -0
- dtSpark/database/usage.py +167 -0
- dtSpark/files/__init__.py +4 -0
- dtSpark/files/manager.py +322 -0
- dtSpark/launch.py +39 -0
- dtSpark/limits/__init__.py +10 -0
- dtSpark/limits/costs.py +296 -0
- dtSpark/limits/tokens.py +342 -0
- dtSpark/llm/__init__.py +17 -0
- dtSpark/llm/anthropic_direct.py +446 -0
- dtSpark/llm/base.py +146 -0
- dtSpark/llm/context_limits.py +438 -0
- dtSpark/llm/manager.py +177 -0
- dtSpark/llm/ollama.py +578 -0
- dtSpark/mcp_integration/__init__.py +5 -0
- dtSpark/mcp_integration/manager.py +653 -0
- dtSpark/mcp_integration/tool_selector.py +225 -0
- dtSpark/resources/config.yaml.template +631 -0
- dtSpark/safety/__init__.py +22 -0
- dtSpark/safety/llm_service.py +111 -0
- dtSpark/safety/patterns.py +229 -0
- dtSpark/safety/prompt_inspector.py +442 -0
- dtSpark/safety/violation_logger.py +346 -0
- dtSpark/scheduler/__init__.py +20 -0
- dtSpark/scheduler/creation_tools.py +599 -0
- dtSpark/scheduler/execution_queue.py +159 -0
- dtSpark/scheduler/executor.py +1152 -0
- dtSpark/scheduler/manager.py +395 -0
- dtSpark/tools/__init__.py +4 -0
- dtSpark/tools/builtin.py +833 -0
- dtSpark/web/__init__.py +20 -0
- dtSpark/web/auth.py +152 -0
- dtSpark/web/dependencies.py +37 -0
- dtSpark/web/endpoints/__init__.py +17 -0
- dtSpark/web/endpoints/autonomous_actions.py +1125 -0
- dtSpark/web/endpoints/chat.py +621 -0
- dtSpark/web/endpoints/conversations.py +353 -0
- dtSpark/web/endpoints/main_menu.py +547 -0
- dtSpark/web/endpoints/streaming.py +421 -0
- dtSpark/web/server.py +578 -0
- dtSpark/web/session.py +167 -0
- dtSpark/web/ssl_utils.py +195 -0
- dtSpark/web/static/css/dark-theme.css +427 -0
- dtSpark/web/static/js/actions.js +1101 -0
- dtSpark/web/static/js/chat.js +614 -0
- dtSpark/web/static/js/main.js +496 -0
- dtSpark/web/static/js/sse-client.js +242 -0
- dtSpark/web/templates/actions.html +408 -0
- dtSpark/web/templates/base.html +93 -0
- dtSpark/web/templates/chat.html +814 -0
- dtSpark/web/templates/conversations.html +350 -0
- dtSpark/web/templates/goodbye.html +81 -0
- dtSpark/web/templates/login.html +90 -0
- dtSpark/web/templates/main_menu.html +983 -0
- dtSpark/web/templates/new_conversation.html +191 -0
- dtSpark/web/web_interface.py +137 -0
- dtspark-1.0.4.dist-info/METADATA +187 -0
- dtspark-1.0.4.dist-info/RECORD +96 -0
- dtspark-1.0.4.dist-info/WHEEL +5 -0
- dtspark-1.0.4.dist-info/entry_points.txt +3 -0
- dtspark-1.0.4.dist-info/licenses/LICENSE +21 -0
- dtspark-1.0.4.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,547 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Main menu API endpoints.
|
|
3
|
+
|
|
4
|
+
Provides REST API for main menu operations:
|
|
5
|
+
- Re-gather AWS Bedrock costs
|
|
6
|
+
- Get account information
|
|
7
|
+
- Get MCP server status
|
|
8
|
+
- Application status
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import logging
|
|
14
|
+
from typing import Optional
|
|
15
|
+
|
|
16
|
+
from fastapi import APIRouter, Depends, Request, HTTPException
|
|
17
|
+
from pydantic import BaseModel
|
|
18
|
+
|
|
19
|
+
from ..dependencies import get_current_session
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
logger = logging.getLogger(__name__)
|
|
23
|
+
|
|
24
|
+
router = APIRouter()
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class AccountInfo(BaseModel):
|
|
28
|
+
"""Account information for configured provider."""
|
|
29
|
+
provider: str
|
|
30
|
+
user_arn: Optional[str] = None
|
|
31
|
+
account_id: Optional[str] = None
|
|
32
|
+
region: Optional[str] = None
|
|
33
|
+
user_guid: str
|
|
34
|
+
auth_method: Optional[str] = None
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class CostInfo(BaseModel):
|
|
38
|
+
"""Cost information for a specific period."""
|
|
39
|
+
total: float
|
|
40
|
+
models: dict[str, dict] # model_id -> {cost, percentage}
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class MCPServerInfo(BaseModel):
|
|
44
|
+
"""MCP server information."""
|
|
45
|
+
name: str
|
|
46
|
+
transport: str
|
|
47
|
+
connected: bool
|
|
48
|
+
tool_count: int
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class ProviderModelInfo(BaseModel):
|
|
52
|
+
"""Model information for a provider."""
|
|
53
|
+
model_id: str
|
|
54
|
+
display_name: str
|
|
55
|
+
description: Optional[str] = None
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class ProviderInfo(BaseModel):
|
|
59
|
+
"""Information about a configured LLM provider."""
|
|
60
|
+
name: str
|
|
61
|
+
type: str # 'aws', 'anthropic', 'ollama'
|
|
62
|
+
enabled: bool
|
|
63
|
+
status: str # 'connected', 'error', 'disabled'
|
|
64
|
+
models: list[ProviderModelInfo] = []
|
|
65
|
+
auth_method: Optional[str] = None
|
|
66
|
+
region: Optional[str] = None
|
|
67
|
+
base_url: Optional[str] = None
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@router.get("/account")
|
|
71
|
+
async def get_account_info(
|
|
72
|
+
request: Request,
|
|
73
|
+
session_id: str = Depends(get_current_session),
|
|
74
|
+
) -> AccountInfo:
|
|
75
|
+
"""
|
|
76
|
+
Get account information for the configured provider.
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
AccountInfo with provider-specific details
|
|
80
|
+
"""
|
|
81
|
+
try:
|
|
82
|
+
app_instance = request.app.state.app_instance
|
|
83
|
+
|
|
84
|
+
# Get LLM manager to determine active provider
|
|
85
|
+
llm_manager = getattr(app_instance, 'llm_manager', None)
|
|
86
|
+
user_guid = getattr(app_instance, 'user_guid', 'unknown')
|
|
87
|
+
|
|
88
|
+
if llm_manager and llm_manager.active_provider:
|
|
89
|
+
active_provider = llm_manager.active_provider.lower()
|
|
90
|
+
|
|
91
|
+
# AWS Bedrock
|
|
92
|
+
if 'bedrock' in active_provider or 'aws' in active_provider:
|
|
93
|
+
auth = getattr(app_instance, 'authenticator', None)
|
|
94
|
+
if auth:
|
|
95
|
+
account_info = auth.get_account_info()
|
|
96
|
+
if account_info:
|
|
97
|
+
return AccountInfo(
|
|
98
|
+
provider='aws',
|
|
99
|
+
user_arn=account_info.get('user_arn'),
|
|
100
|
+
account_id=account_info.get('account_id'),
|
|
101
|
+
region=account_info.get('region'),
|
|
102
|
+
user_guid=user_guid,
|
|
103
|
+
auth_method=account_info.get('auth_method'),
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
# Anthropic
|
|
107
|
+
elif 'anthropic' in active_provider:
|
|
108
|
+
return AccountInfo(
|
|
109
|
+
provider='anthropic',
|
|
110
|
+
user_guid=user_guid,
|
|
111
|
+
auth_method='api_key',
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
# Ollama
|
|
115
|
+
elif 'ollama' in active_provider:
|
|
116
|
+
return AccountInfo(
|
|
117
|
+
provider='ollama',
|
|
118
|
+
user_guid=user_guid,
|
|
119
|
+
auth_method='local',
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
# No provider configured - return basic info
|
|
123
|
+
return AccountInfo(
|
|
124
|
+
provider='none',
|
|
125
|
+
user_guid=user_guid,
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
except HTTPException:
|
|
129
|
+
raise
|
|
130
|
+
except Exception as e:
|
|
131
|
+
logger.error(f"Error getting account info: {e}")
|
|
132
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
@router.get("/providers")
|
|
136
|
+
async def get_providers(
|
|
137
|
+
request: Request,
|
|
138
|
+
session_id: str = Depends(get_current_session),
|
|
139
|
+
) -> list[ProviderInfo]:
|
|
140
|
+
"""
|
|
141
|
+
Get all configured LLM providers and their available models.
|
|
142
|
+
|
|
143
|
+
Returns:
|
|
144
|
+
List of ProviderInfo with provider details and available models
|
|
145
|
+
"""
|
|
146
|
+
try:
|
|
147
|
+
app_instance = request.app.state.app_instance
|
|
148
|
+
providers = []
|
|
149
|
+
|
|
150
|
+
# Get LLM manager which has all registered providers
|
|
151
|
+
llm_manager = getattr(app_instance, 'llm_manager', None)
|
|
152
|
+
|
|
153
|
+
if llm_manager and hasattr(llm_manager, 'providers'):
|
|
154
|
+
for provider_name, service in llm_manager.providers.items():
|
|
155
|
+
models = []
|
|
156
|
+
status = 'connected'
|
|
157
|
+
provider_type = 'unknown'
|
|
158
|
+
auth_method = None
|
|
159
|
+
region = None
|
|
160
|
+
base_url = None
|
|
161
|
+
|
|
162
|
+
# Determine provider type
|
|
163
|
+
provider_name_lower = provider_name.lower()
|
|
164
|
+
if 'bedrock' in provider_name_lower or 'aws' in provider_name_lower:
|
|
165
|
+
provider_type = 'aws'
|
|
166
|
+
auth = getattr(app_instance, 'authenticator', None)
|
|
167
|
+
if auth:
|
|
168
|
+
account_info = auth.get_account_info()
|
|
169
|
+
if account_info:
|
|
170
|
+
auth_method = account_info.get('auth_method')
|
|
171
|
+
region = account_info.get('region')
|
|
172
|
+
elif 'anthropic' in provider_name_lower:
|
|
173
|
+
provider_type = 'anthropic'
|
|
174
|
+
auth_method = 'api_key'
|
|
175
|
+
elif 'ollama' in provider_name_lower:
|
|
176
|
+
provider_type = 'ollama'
|
|
177
|
+
auth_method = 'local'
|
|
178
|
+
base_url = getattr(service, 'base_url', 'http://localhost:11434')
|
|
179
|
+
|
|
180
|
+
# Get available models
|
|
181
|
+
try:
|
|
182
|
+
if hasattr(service, 'list_available_models'):
|
|
183
|
+
available_models = service.list_available_models()
|
|
184
|
+
elif hasattr(service, 'list_models'):
|
|
185
|
+
available_models = service.list_models()
|
|
186
|
+
else:
|
|
187
|
+
available_models = []
|
|
188
|
+
|
|
189
|
+
for model in available_models:
|
|
190
|
+
if isinstance(model, dict):
|
|
191
|
+
model_id = model.get('id') or model.get('modelId') or model.get('name') or str(model)
|
|
192
|
+
display_name = model.get('display_name') or model.get('modelName') or model_id
|
|
193
|
+
else:
|
|
194
|
+
model_id = str(model)
|
|
195
|
+
display_name = model_id
|
|
196
|
+
|
|
197
|
+
models.append(ProviderModelInfo(
|
|
198
|
+
model_id=model_id,
|
|
199
|
+
display_name=display_name,
|
|
200
|
+
))
|
|
201
|
+
except Exception as e:
|
|
202
|
+
logger.warning(f"Failed to list models from {provider_name}: {e}")
|
|
203
|
+
status = 'error'
|
|
204
|
+
|
|
205
|
+
providers.append(ProviderInfo(
|
|
206
|
+
name=provider_name,
|
|
207
|
+
type=provider_type,
|
|
208
|
+
enabled=True,
|
|
209
|
+
status=status,
|
|
210
|
+
models=models,
|
|
211
|
+
auth_method=auth_method,
|
|
212
|
+
region=region,
|
|
213
|
+
base_url=base_url,
|
|
214
|
+
))
|
|
215
|
+
|
|
216
|
+
return providers
|
|
217
|
+
|
|
218
|
+
except Exception as e:
|
|
219
|
+
logger.error(f"Error getting providers: {e}")
|
|
220
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
@router.get("/costs/last-month")
|
|
224
|
+
async def get_last_month_costs(
|
|
225
|
+
request: Request,
|
|
226
|
+
session_id: str = Depends(get_current_session),
|
|
227
|
+
) -> CostInfo:
|
|
228
|
+
"""
|
|
229
|
+
Get AWS Bedrock costs for the last month.
|
|
230
|
+
|
|
231
|
+
Returns:
|
|
232
|
+
CostInfo with total cost and per-model breakdown
|
|
233
|
+
"""
|
|
234
|
+
try:
|
|
235
|
+
app_instance = request.app.state.app_instance
|
|
236
|
+
|
|
237
|
+
# Get costs from app instance (cached from startup)
|
|
238
|
+
if not hasattr(app_instance, 'bedrock_costs') or not app_instance.bedrock_costs:
|
|
239
|
+
return CostInfo(total=0.0, models={})
|
|
240
|
+
|
|
241
|
+
last_month_data = app_instance.bedrock_costs.get('last_month', {})
|
|
242
|
+
|
|
243
|
+
# Extract total and models from the cost data structure
|
|
244
|
+
total = last_month_data.get('total', 0.0)
|
|
245
|
+
models_breakdown = last_month_data.get('breakdown', {})
|
|
246
|
+
|
|
247
|
+
# Format per-model costs
|
|
248
|
+
models = {}
|
|
249
|
+
for model_id, cost in models_breakdown.items():
|
|
250
|
+
percentage = (cost / total * 100) if total > 0 else 0
|
|
251
|
+
|
|
252
|
+
models[model_id] = {
|
|
253
|
+
'cost': cost,
|
|
254
|
+
'percentage': round(percentage, 2),
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
return CostInfo(
|
|
258
|
+
total=round(total, 2),
|
|
259
|
+
models=models,
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
except Exception as e:
|
|
263
|
+
logger.error(f"Error getting last month costs: {e}")
|
|
264
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
@router.get("/costs/last-24-hours")
|
|
268
|
+
async def get_last_24_hours_costs(
|
|
269
|
+
request: Request,
|
|
270
|
+
session_id: str = Depends(get_current_session),
|
|
271
|
+
) -> CostInfo:
|
|
272
|
+
"""
|
|
273
|
+
Get AWS Bedrock costs for the last 24 hours.
|
|
274
|
+
|
|
275
|
+
Returns:
|
|
276
|
+
CostInfo with total cost and per-model breakdown
|
|
277
|
+
"""
|
|
278
|
+
try:
|
|
279
|
+
app_instance = request.app.state.app_instance
|
|
280
|
+
|
|
281
|
+
# Get costs from app instance (cached from startup)
|
|
282
|
+
if not hasattr(app_instance, 'bedrock_costs') or not app_instance.bedrock_costs:
|
|
283
|
+
return CostInfo(total=0.0, models={})
|
|
284
|
+
|
|
285
|
+
last_24h_data = app_instance.bedrock_costs.get('last_24h', {})
|
|
286
|
+
|
|
287
|
+
# Extract total and models from the cost data structure
|
|
288
|
+
total = last_24h_data.get('total', 0.0)
|
|
289
|
+
models_breakdown = last_24h_data.get('breakdown', {})
|
|
290
|
+
|
|
291
|
+
# Format per-model costs
|
|
292
|
+
models = {}
|
|
293
|
+
for model_id, cost in models_breakdown.items():
|
|
294
|
+
percentage = (cost / total * 100) if total > 0 else 0
|
|
295
|
+
|
|
296
|
+
models[model_id] = {
|
|
297
|
+
'cost': cost,
|
|
298
|
+
'percentage': round(percentage, 2),
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
return CostInfo(
|
|
302
|
+
total=round(total, 2),
|
|
303
|
+
models=models,
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
except Exception as e:
|
|
307
|
+
logger.error(f"Error getting last 24 hours costs: {e}")
|
|
308
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
@router.post("/costs/refresh")
|
|
312
|
+
async def refresh_costs(
|
|
313
|
+
request: Request,
|
|
314
|
+
session_id: str = Depends(get_current_session),
|
|
315
|
+
) -> dict:
|
|
316
|
+
"""
|
|
317
|
+
Refresh AWS Bedrock cost information.
|
|
318
|
+
|
|
319
|
+
Returns:
|
|
320
|
+
Status message
|
|
321
|
+
"""
|
|
322
|
+
try:
|
|
323
|
+
app_instance = request.app.state.app_instance
|
|
324
|
+
|
|
325
|
+
# Re-gather costs
|
|
326
|
+
if hasattr(app_instance, 'cost_tracker') and app_instance.cost_tracker:
|
|
327
|
+
app_instance.bedrock_costs = app_instance.cost_tracker.get_bedrock_costs()
|
|
328
|
+
|
|
329
|
+
return {
|
|
330
|
+
"status": "success",
|
|
331
|
+
"message": "Costs refreshed successfully",
|
|
332
|
+
}
|
|
333
|
+
else:
|
|
334
|
+
return {
|
|
335
|
+
"status": "error",
|
|
336
|
+
"message": "Cost tracker not available",
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
except Exception as e:
|
|
340
|
+
logger.error(f"Error refreshing costs: {e}")
|
|
341
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
@router.get("/mcp/servers")
|
|
345
|
+
async def get_mcp_servers(
|
|
346
|
+
request: Request,
|
|
347
|
+
session_id: str = Depends(get_current_session),
|
|
348
|
+
) -> list[MCPServerInfo]:
|
|
349
|
+
"""
|
|
350
|
+
Get MCP server status and information.
|
|
351
|
+
|
|
352
|
+
Returns:
|
|
353
|
+
List of MCPServerInfo with connection status and tool counts
|
|
354
|
+
"""
|
|
355
|
+
try:
|
|
356
|
+
app_instance = request.app.state.app_instance
|
|
357
|
+
|
|
358
|
+
# Check if MCP is enabled
|
|
359
|
+
if not hasattr(app_instance, 'mcp_manager') or not app_instance.mcp_manager:
|
|
360
|
+
return []
|
|
361
|
+
|
|
362
|
+
servers = []
|
|
363
|
+
mcp_manager = app_instance.mcp_manager
|
|
364
|
+
|
|
365
|
+
# Get all tools from MCP servers (async call)
|
|
366
|
+
all_tools = await mcp_manager.list_all_tools()
|
|
367
|
+
|
|
368
|
+
# Count tools by server
|
|
369
|
+
tool_counts = {}
|
|
370
|
+
for tool in all_tools:
|
|
371
|
+
server = tool.get('server', 'unknown')
|
|
372
|
+
tool_counts[server] = tool_counts.get(server, 0) + 1
|
|
373
|
+
|
|
374
|
+
# Get information for each server
|
|
375
|
+
if hasattr(mcp_manager, 'clients'):
|
|
376
|
+
for server_name, client in mcp_manager.clients.items():
|
|
377
|
+
# Determine transport type
|
|
378
|
+
transport = 'stdio'
|
|
379
|
+
if hasattr(client, 'config') and hasattr(client.config, 'transport'):
|
|
380
|
+
transport = client.config.transport
|
|
381
|
+
|
|
382
|
+
servers.append(
|
|
383
|
+
MCPServerInfo(
|
|
384
|
+
name=server_name,
|
|
385
|
+
transport=transport,
|
|
386
|
+
connected=client.connected if hasattr(client, 'connected') else True,
|
|
387
|
+
tool_count=tool_counts.get(server_name, 0),
|
|
388
|
+
)
|
|
389
|
+
)
|
|
390
|
+
|
|
391
|
+
return servers
|
|
392
|
+
|
|
393
|
+
except Exception as e:
|
|
394
|
+
logger.error(f"Error getting MCP servers: {e}")
|
|
395
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
@router.get("/tools")
|
|
399
|
+
async def get_all_tools(
|
|
400
|
+
request: Request,
|
|
401
|
+
session_id: str = Depends(get_current_session),
|
|
402
|
+
) -> dict:
|
|
403
|
+
"""
|
|
404
|
+
Get all available tools (MCP + embedded).
|
|
405
|
+
|
|
406
|
+
Returns:
|
|
407
|
+
Dictionary with 'tools' list containing all available tools
|
|
408
|
+
"""
|
|
409
|
+
try:
|
|
410
|
+
app_instance = request.app.state.app_instance
|
|
411
|
+
all_tools = []
|
|
412
|
+
|
|
413
|
+
# Get MCP tools
|
|
414
|
+
if hasattr(app_instance, 'mcp_manager') and app_instance.mcp_manager:
|
|
415
|
+
try:
|
|
416
|
+
mcp_tools = await app_instance.mcp_manager.list_all_tools()
|
|
417
|
+
for tool in mcp_tools:
|
|
418
|
+
all_tools.append({
|
|
419
|
+
'name': tool.get('name', 'unknown'),
|
|
420
|
+
'description': tool.get('description', ''),
|
|
421
|
+
'server': tool.get('server', ''),
|
|
422
|
+
'source': 'mcp',
|
|
423
|
+
})
|
|
424
|
+
except Exception as e:
|
|
425
|
+
logger.warning(f"Error getting MCP tools: {e}")
|
|
426
|
+
|
|
427
|
+
# Get embedded tools
|
|
428
|
+
if hasattr(app_instance, 'conversation_manager') and app_instance.conversation_manager:
|
|
429
|
+
try:
|
|
430
|
+
embedded = app_instance.conversation_manager.get_embedded_tools()
|
|
431
|
+
for tool in embedded:
|
|
432
|
+
tool_spec = tool.get('toolSpec', {})
|
|
433
|
+
all_tools.append({
|
|
434
|
+
'name': tool_spec.get('name', 'unknown'),
|
|
435
|
+
'description': tool_spec.get('description', ''),
|
|
436
|
+
'server': '',
|
|
437
|
+
'source': 'embedded',
|
|
438
|
+
})
|
|
439
|
+
except Exception as e:
|
|
440
|
+
logger.warning(f"Error getting embedded tools: {e}")
|
|
441
|
+
|
|
442
|
+
return {'tools': all_tools}
|
|
443
|
+
|
|
444
|
+
except Exception as e:
|
|
445
|
+
logger.error(f"Error getting tools: {e}")
|
|
446
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
447
|
+
|
|
448
|
+
|
|
449
|
+
@router.get("/mcp/tools")
|
|
450
|
+
async def get_mcp_tools(
|
|
451
|
+
request: Request,
|
|
452
|
+
server: Optional[str] = None,
|
|
453
|
+
session_id: str = Depends(get_current_session),
|
|
454
|
+
) -> list[dict]:
|
|
455
|
+
"""
|
|
456
|
+
Get available tools from MCP servers.
|
|
457
|
+
|
|
458
|
+
Args:
|
|
459
|
+
server: Optional server name to filter tools by
|
|
460
|
+
|
|
461
|
+
Returns:
|
|
462
|
+
List of tool definitions with name, description, and input schema
|
|
463
|
+
"""
|
|
464
|
+
try:
|
|
465
|
+
app_instance = request.app.state.app_instance
|
|
466
|
+
|
|
467
|
+
# Check if MCP is enabled
|
|
468
|
+
if not hasattr(app_instance, 'mcp_manager') or not app_instance.mcp_manager:
|
|
469
|
+
return []
|
|
470
|
+
|
|
471
|
+
mcp_manager = app_instance.mcp_manager
|
|
472
|
+
|
|
473
|
+
# Get all tools from MCP servers
|
|
474
|
+
all_tools = await mcp_manager.list_all_tools()
|
|
475
|
+
|
|
476
|
+
# Filter by server if specified
|
|
477
|
+
if server:
|
|
478
|
+
all_tools = [t for t in all_tools if t.get('server') == server]
|
|
479
|
+
|
|
480
|
+
# Format tool information
|
|
481
|
+
tools = []
|
|
482
|
+
for tool in all_tools:
|
|
483
|
+
tools.append({
|
|
484
|
+
'name': tool.get('name', 'unknown'),
|
|
485
|
+
'description': tool.get('description', ''),
|
|
486
|
+
'server': tool.get('server', 'unknown'),
|
|
487
|
+
'input_schema': tool.get('inputSchema', {}),
|
|
488
|
+
})
|
|
489
|
+
|
|
490
|
+
return tools
|
|
491
|
+
|
|
492
|
+
except Exception as e:
|
|
493
|
+
logger.error(f"Error getting MCP tools: {e}")
|
|
494
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
495
|
+
|
|
496
|
+
|
|
497
|
+
@router.get("/daemon/status")
|
|
498
|
+
async def get_daemon_status(request: Request):
|
|
499
|
+
"""
|
|
500
|
+
Get daemon status.
|
|
501
|
+
|
|
502
|
+
Returns:
|
|
503
|
+
Dictionary with daemon status information
|
|
504
|
+
"""
|
|
505
|
+
try:
|
|
506
|
+
app_instance = request.app.state.app_instance
|
|
507
|
+
|
|
508
|
+
# Check if daemon is running using PID file
|
|
509
|
+
daemon_running = False
|
|
510
|
+
daemon_pid = None
|
|
511
|
+
|
|
512
|
+
try:
|
|
513
|
+
from dtSpark.daemon.pid_file import PIDFile
|
|
514
|
+
from dtPyAppFramework.settings import Settings
|
|
515
|
+
|
|
516
|
+
settings = app_instance.settings if hasattr(app_instance, 'settings') else Settings()
|
|
517
|
+
pid_file_path = settings.get('daemon.pid_file', './daemon.pid')
|
|
518
|
+
pid_file = PIDFile(pid_file_path)
|
|
519
|
+
|
|
520
|
+
daemon_running = pid_file.is_running()
|
|
521
|
+
if daemon_running:
|
|
522
|
+
daemon_pid = pid_file.read_pid()
|
|
523
|
+
|
|
524
|
+
except Exception as e:
|
|
525
|
+
logger.warning(f"Error checking daemon status: {e}")
|
|
526
|
+
|
|
527
|
+
# Count scheduled actions
|
|
528
|
+
scheduled_count = 0
|
|
529
|
+
try:
|
|
530
|
+
actions = app_instance.database.get_all_actions(include_disabled=False)
|
|
531
|
+
scheduled_count = sum(1 for a in actions if a.get('schedule_type') != 'manual')
|
|
532
|
+
except Exception:
|
|
533
|
+
pass
|
|
534
|
+
|
|
535
|
+
return {
|
|
536
|
+
'daemon_running': daemon_running,
|
|
537
|
+
'daemon_pid': daemon_pid,
|
|
538
|
+
'scheduled_actions_count': scheduled_count,
|
|
539
|
+
'warning': None if daemon_running else (
|
|
540
|
+
f"Daemon is not running - {scheduled_count} scheduled action(s) will not execute"
|
|
541
|
+
if scheduled_count > 0 else None
|
|
542
|
+
)
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
except Exception as e:
|
|
546
|
+
logger.error(f"Error getting daemon status: {e}")
|
|
547
|
+
raise HTTPException(status_code=500, detail=str(e))
|