ostruct-cli 0.7.1__py3-none-any.whl → 0.8.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.
- ostruct/cli/__init__.py +21 -3
- ostruct/cli/base_errors.py +1 -1
- ostruct/cli/cli.py +66 -1983
- ostruct/cli/click_options.py +460 -28
- ostruct/cli/code_interpreter.py +238 -0
- ostruct/cli/commands/__init__.py +32 -0
- ostruct/cli/commands/list_models.py +128 -0
- ostruct/cli/commands/quick_ref.py +50 -0
- ostruct/cli/commands/run.py +137 -0
- ostruct/cli/commands/update_registry.py +71 -0
- ostruct/cli/config.py +277 -0
- ostruct/cli/cost_estimation.py +134 -0
- ostruct/cli/errors.py +310 -6
- ostruct/cli/exit_codes.py +1 -0
- ostruct/cli/explicit_file_processor.py +548 -0
- ostruct/cli/field_utils.py +69 -0
- ostruct/cli/file_info.py +42 -9
- ostruct/cli/file_list.py +301 -102
- ostruct/cli/file_search.py +455 -0
- ostruct/cli/file_utils.py +47 -13
- ostruct/cli/mcp_integration.py +541 -0
- ostruct/cli/model_creation.py +150 -1
- ostruct/cli/model_validation.py +204 -0
- ostruct/cli/progress_reporting.py +398 -0
- ostruct/cli/registry_updates.py +14 -9
- ostruct/cli/runner.py +1418 -0
- ostruct/cli/schema_utils.py +113 -0
- ostruct/cli/services.py +626 -0
- ostruct/cli/template_debug.py +748 -0
- ostruct/cli/template_debug_help.py +162 -0
- ostruct/cli/template_env.py +15 -6
- ostruct/cli/template_filters.py +55 -3
- ostruct/cli/template_optimizer.py +474 -0
- ostruct/cli/template_processor.py +1080 -0
- ostruct/cli/template_rendering.py +69 -34
- ostruct/cli/token_validation.py +286 -0
- ostruct/cli/types.py +78 -0
- ostruct/cli/unattended_operation.py +269 -0
- ostruct/cli/validators.py +386 -3
- {ostruct_cli-0.7.1.dist-info → ostruct_cli-0.8.0.dist-info}/LICENSE +2 -0
- ostruct_cli-0.8.0.dist-info/METADATA +633 -0
- ostruct_cli-0.8.0.dist-info/RECORD +69 -0
- {ostruct_cli-0.7.1.dist-info → ostruct_cli-0.8.0.dist-info}/WHEEL +1 -1
- ostruct_cli-0.7.1.dist-info/METADATA +0 -369
- ostruct_cli-0.7.1.dist-info/RECORD +0 -45
- {ostruct_cli-0.7.1.dist-info → ostruct_cli-0.8.0.dist-info}/entry_points.txt +0 -0
ostruct/cli/services.py
ADDED
@@ -0,0 +1,626 @@
|
|
1
|
+
"""Service container for ostruct CLI tool managers."""
|
2
|
+
|
3
|
+
import logging
|
4
|
+
from abc import abstractmethod
|
5
|
+
from dataclasses import dataclass
|
6
|
+
from enum import Enum
|
7
|
+
from typing import Any, Dict, List, Optional, Protocol
|
8
|
+
|
9
|
+
from openai import AsyncOpenAI
|
10
|
+
from pydantic import BaseModel, ConfigDict, Field, ValidationError
|
11
|
+
|
12
|
+
from .types import CLIParams
|
13
|
+
|
14
|
+
logger = logging.getLogger(__name__)
|
15
|
+
|
16
|
+
# Type alias for CLI parameters
|
17
|
+
# CLIParams imported from types module
|
18
|
+
|
19
|
+
|
20
|
+
class ServiceStatus(Enum):
|
21
|
+
"""Service health status."""
|
22
|
+
|
23
|
+
HEALTHY = "healthy"
|
24
|
+
DEGRADED = "degraded"
|
25
|
+
UNHEALTHY = "unhealthy"
|
26
|
+
UNKNOWN = "unknown"
|
27
|
+
|
28
|
+
|
29
|
+
@dataclass
|
30
|
+
class ServiceHealth:
|
31
|
+
"""Service health information."""
|
32
|
+
|
33
|
+
status: ServiceStatus
|
34
|
+
message: str
|
35
|
+
details: Optional[Dict[str, Any]] = None
|
36
|
+
|
37
|
+
|
38
|
+
class ServiceConfigurationBase(BaseModel):
|
39
|
+
"""Base configuration for all services."""
|
40
|
+
|
41
|
+
model_config = ConfigDict(
|
42
|
+
extra="allow"
|
43
|
+
) # Allow additional fields for service-specific config
|
44
|
+
|
45
|
+
enabled: bool = Field(
|
46
|
+
default=True, description="Whether the service is enabled"
|
47
|
+
)
|
48
|
+
timeout: Optional[float] = Field(
|
49
|
+
default=None, description="Service timeout in seconds"
|
50
|
+
)
|
51
|
+
retry_attempts: int = Field(
|
52
|
+
default=3, description="Number of retry attempts"
|
53
|
+
)
|
54
|
+
|
55
|
+
|
56
|
+
class MCPServiceConfiguration(ServiceConfigurationBase):
|
57
|
+
"""Configuration for MCP service."""
|
58
|
+
|
59
|
+
servers: List[Dict[str, Any]] = Field(
|
60
|
+
default_factory=list, description="MCP server configurations"
|
61
|
+
)
|
62
|
+
connection_timeout: float = Field(
|
63
|
+
default=30.0, description="Connection timeout in seconds"
|
64
|
+
)
|
65
|
+
max_retries: int = Field(
|
66
|
+
default=3, description="Maximum retry attempts for server connections"
|
67
|
+
)
|
68
|
+
|
69
|
+
def validate_servers(self) -> List[str]:
|
70
|
+
"""Validate server configurations and return any errors."""
|
71
|
+
errors = []
|
72
|
+
for i, server in enumerate(self.servers):
|
73
|
+
if not isinstance(server, dict):
|
74
|
+
errors.append(f"Server {i}: Must be a dictionary")
|
75
|
+
continue
|
76
|
+
if "name" not in server:
|
77
|
+
errors.append(f"Server {i}: Missing required 'name' field")
|
78
|
+
if "command" not in server:
|
79
|
+
errors.append(f"Server {i}: Missing required 'command' field")
|
80
|
+
return errors
|
81
|
+
|
82
|
+
|
83
|
+
class CodeInterpreterServiceConfiguration(ServiceConfigurationBase):
|
84
|
+
"""Configuration for Code Interpreter service."""
|
85
|
+
|
86
|
+
max_file_size_mb: float = Field(
|
87
|
+
default=100.0, description="Maximum file size in MB"
|
88
|
+
)
|
89
|
+
allowed_file_types: List[str] = Field(
|
90
|
+
default_factory=lambda: [".py", ".txt", ".json", ".csv", ".md"],
|
91
|
+
description="Allowed file extensions",
|
92
|
+
)
|
93
|
+
cleanup_on_exit: bool = Field(
|
94
|
+
default=True, description="Clean up uploaded files on exit"
|
95
|
+
)
|
96
|
+
|
97
|
+
def validate_file_types(self) -> List[str]:
|
98
|
+
"""Validate file type configurations."""
|
99
|
+
errors = []
|
100
|
+
for file_type in self.allowed_file_types:
|
101
|
+
if not isinstance(file_type, str):
|
102
|
+
errors.append(
|
103
|
+
f"File type must be string, got {type(file_type)}"
|
104
|
+
)
|
105
|
+
elif not file_type.startswith("."):
|
106
|
+
errors.append(f"File type '{file_type}' must start with '.'")
|
107
|
+
return errors
|
108
|
+
|
109
|
+
|
110
|
+
class FileSearchServiceConfiguration(ServiceConfigurationBase):
|
111
|
+
"""Configuration for File Search service."""
|
112
|
+
|
113
|
+
max_files: int = Field(
|
114
|
+
default=1000, description="Maximum number of files to index"
|
115
|
+
)
|
116
|
+
chunk_size: int = Field(
|
117
|
+
default=800, description="Text chunk size for indexing"
|
118
|
+
)
|
119
|
+
overlap: int = Field(default=400, description="Text chunk overlap")
|
120
|
+
cleanup_vector_stores: bool = Field(
|
121
|
+
default=True, description="Clean up vector stores on exit"
|
122
|
+
)
|
123
|
+
|
124
|
+
def validate_chunk_settings(self) -> List[str]:
|
125
|
+
"""Validate chunk size and overlap settings."""
|
126
|
+
errors = []
|
127
|
+
if self.chunk_size <= 0:
|
128
|
+
errors.append("Chunk size must be positive")
|
129
|
+
if self.overlap < 0:
|
130
|
+
errors.append("Overlap cannot be negative")
|
131
|
+
if self.overlap >= self.chunk_size:
|
132
|
+
errors.append("Overlap must be less than chunk size")
|
133
|
+
return errors
|
134
|
+
|
135
|
+
|
136
|
+
class ServiceConfigurationValidator:
|
137
|
+
"""Validates service configurations."""
|
138
|
+
|
139
|
+
@staticmethod
|
140
|
+
def validate_mcp_config(config: Dict[str, Any]) -> MCPServiceConfiguration:
|
141
|
+
"""Validate MCP service configuration."""
|
142
|
+
try:
|
143
|
+
mcp_config = MCPServiceConfiguration(**config)
|
144
|
+
server_errors = mcp_config.validate_servers()
|
145
|
+
if server_errors:
|
146
|
+
raise ValueError(
|
147
|
+
f"MCP server validation errors: {'; '.join(server_errors)}"
|
148
|
+
)
|
149
|
+
return mcp_config
|
150
|
+
except (ValidationError, ValueError) as e:
|
151
|
+
logger.error(f"MCP configuration validation failed: {e}")
|
152
|
+
raise
|
153
|
+
|
154
|
+
@staticmethod
|
155
|
+
def validate_code_interpreter_config(
|
156
|
+
config: Dict[str, Any],
|
157
|
+
) -> CodeInterpreterServiceConfiguration:
|
158
|
+
"""Validate Code Interpreter service configuration."""
|
159
|
+
try:
|
160
|
+
ci_config = CodeInterpreterServiceConfiguration(**config)
|
161
|
+
file_type_errors = ci_config.validate_file_types()
|
162
|
+
if file_type_errors:
|
163
|
+
raise ValueError(
|
164
|
+
f"Code Interpreter file type errors: {'; '.join(file_type_errors)}"
|
165
|
+
)
|
166
|
+
return ci_config
|
167
|
+
except (ValidationError, ValueError) as e:
|
168
|
+
logger.error(
|
169
|
+
f"Code Interpreter configuration validation failed: {e}"
|
170
|
+
)
|
171
|
+
raise
|
172
|
+
|
173
|
+
@staticmethod
|
174
|
+
def validate_file_search_config(
|
175
|
+
config: Dict[str, Any],
|
176
|
+
) -> FileSearchServiceConfiguration:
|
177
|
+
"""Validate File Search service configuration."""
|
178
|
+
try:
|
179
|
+
fs_config = FileSearchServiceConfiguration(**config)
|
180
|
+
chunk_errors = fs_config.validate_chunk_settings()
|
181
|
+
if chunk_errors:
|
182
|
+
raise ValueError(
|
183
|
+
f"File Search chunk setting errors: {'; '.join(chunk_errors)}"
|
184
|
+
)
|
185
|
+
return fs_config
|
186
|
+
except (ValidationError, ValueError) as e:
|
187
|
+
logger.error(f"File Search configuration validation failed: {e}")
|
188
|
+
raise
|
189
|
+
|
190
|
+
|
191
|
+
class ToolManagerProtocol(Protocol):
|
192
|
+
"""Protocol defining the interface for tool managers."""
|
193
|
+
|
194
|
+
@abstractmethod
|
195
|
+
async def cleanup(self) -> None:
|
196
|
+
"""Clean up resources used by the tool manager."""
|
197
|
+
...
|
198
|
+
|
199
|
+
@abstractmethod
|
200
|
+
async def health_check(self) -> ServiceHealth:
|
201
|
+
"""Check the health status of the tool manager."""
|
202
|
+
...
|
203
|
+
|
204
|
+
|
205
|
+
class MCPManagerProtocol(ToolManagerProtocol):
|
206
|
+
"""Protocol for MCP server managers."""
|
207
|
+
|
208
|
+
@abstractmethod
|
209
|
+
def get_tools_for_responses_api(self) -> List[dict]:
|
210
|
+
"""Get tools configured for OpenAI Responses API."""
|
211
|
+
...
|
212
|
+
|
213
|
+
|
214
|
+
class CodeInterpreterManagerProtocol(ToolManagerProtocol):
|
215
|
+
"""Protocol for code interpreter managers."""
|
216
|
+
|
217
|
+
@abstractmethod
|
218
|
+
async def upload_files_for_code_interpreter(
|
219
|
+
self, files: List[str]
|
220
|
+
) -> List[str]:
|
221
|
+
"""Upload files for code interpreter access."""
|
222
|
+
...
|
223
|
+
|
224
|
+
@abstractmethod
|
225
|
+
async def cleanup_uploaded_files(self) -> None:
|
226
|
+
"""Clean up uploaded files."""
|
227
|
+
...
|
228
|
+
|
229
|
+
@abstractmethod
|
230
|
+
def build_tool_config(self, file_ids: List[str]) -> Dict[str, Any]:
|
231
|
+
"""Build Code Interpreter tool configuration."""
|
232
|
+
...
|
233
|
+
|
234
|
+
|
235
|
+
class FileSearchManagerProtocol(ToolManagerProtocol):
|
236
|
+
"""Protocol for file search managers."""
|
237
|
+
|
238
|
+
@abstractmethod
|
239
|
+
async def upload_files_to_vector_store(
|
240
|
+
self, files: List[str], vector_store_id: str
|
241
|
+
) -> Dict[str, Any]:
|
242
|
+
"""Upload files to vector store."""
|
243
|
+
...
|
244
|
+
|
245
|
+
@abstractmethod
|
246
|
+
async def cleanup_resources(self) -> None:
|
247
|
+
"""Clean up vector store resources."""
|
248
|
+
...
|
249
|
+
|
250
|
+
@abstractmethod
|
251
|
+
def build_tool_config(self, vector_store_id: str) -> Dict[str, Any]:
|
252
|
+
"""Build File Search tool configuration."""
|
253
|
+
...
|
254
|
+
|
255
|
+
|
256
|
+
class ServiceFactoryProtocol(Protocol):
|
257
|
+
"""Protocol for service factories."""
|
258
|
+
|
259
|
+
async def create_mcp_manager(
|
260
|
+
self, args: CLIParams
|
261
|
+
) -> Optional[MCPManagerProtocol]:
|
262
|
+
"""Create MCP server manager."""
|
263
|
+
...
|
264
|
+
|
265
|
+
async def create_code_interpreter_manager(
|
266
|
+
self, args: CLIParams, client: AsyncOpenAI
|
267
|
+
) -> Optional[CodeInterpreterManagerProtocol]:
|
268
|
+
"""Create code interpreter manager."""
|
269
|
+
...
|
270
|
+
|
271
|
+
async def create_file_search_manager(
|
272
|
+
self, args: CLIParams, client: AsyncOpenAI
|
273
|
+
) -> Optional[FileSearchManagerProtocol]:
|
274
|
+
"""Create file search manager."""
|
275
|
+
...
|
276
|
+
|
277
|
+
|
278
|
+
class DefaultServiceFactory:
|
279
|
+
"""Default factory for creating service instances."""
|
280
|
+
|
281
|
+
async def create_mcp_manager(
|
282
|
+
self, args: CLIParams
|
283
|
+
) -> Optional[MCPManagerProtocol]:
|
284
|
+
"""Create MCP server manager."""
|
285
|
+
if not args.get("mcp_servers"):
|
286
|
+
return None
|
287
|
+
from .runner import process_mcp_configuration
|
288
|
+
|
289
|
+
manager = await process_mcp_configuration(args)
|
290
|
+
# The manager implements MCPManagerProtocol interface
|
291
|
+
return manager # type: ignore[return-value]
|
292
|
+
|
293
|
+
async def create_code_interpreter_manager(
|
294
|
+
self, args: CLIParams, client: AsyncOpenAI
|
295
|
+
) -> Optional[CodeInterpreterManagerProtocol]:
|
296
|
+
"""Create code interpreter manager."""
|
297
|
+
if not args.get("code_interpreter"):
|
298
|
+
return None
|
299
|
+
from .runner import process_code_interpreter_configuration
|
300
|
+
|
301
|
+
manager_info = await process_code_interpreter_configuration(
|
302
|
+
args, client
|
303
|
+
)
|
304
|
+
return manager_info.get("manager") if manager_info else None
|
305
|
+
|
306
|
+
async def create_file_search_manager(
|
307
|
+
self, args: CLIParams, client: AsyncOpenAI
|
308
|
+
) -> Optional[FileSearchManagerProtocol]:
|
309
|
+
"""Create file search manager."""
|
310
|
+
if not args.get("file_search"):
|
311
|
+
return None
|
312
|
+
from .runner import process_file_search_configuration
|
313
|
+
|
314
|
+
manager_info = await process_file_search_configuration(args, client)
|
315
|
+
return manager_info.get("manager") if manager_info else None
|
316
|
+
|
317
|
+
|
318
|
+
class ServiceContainer:
|
319
|
+
"""Service container for managing tool managers and their dependencies."""
|
320
|
+
|
321
|
+
def __init__(
|
322
|
+
self,
|
323
|
+
client: AsyncOpenAI,
|
324
|
+
args: CLIParams,
|
325
|
+
factory: Optional[ServiceFactoryProtocol] = None,
|
326
|
+
):
|
327
|
+
"""Initialize service container.
|
328
|
+
|
329
|
+
Args:
|
330
|
+
client: Configured AsyncOpenAI client
|
331
|
+
args: CLI parameters
|
332
|
+
factory: Service factory for creating managers (uses default if None)
|
333
|
+
"""
|
334
|
+
self.client = client
|
335
|
+
self.args = args
|
336
|
+
self.factory = factory or DefaultServiceFactory()
|
337
|
+
self._mcp_manager: Optional[MCPManagerProtocol] = None
|
338
|
+
self._code_interpreter_manager: Optional[
|
339
|
+
CodeInterpreterManagerProtocol
|
340
|
+
] = None
|
341
|
+
self._file_search_manager: Optional[FileSearchManagerProtocol] = None
|
342
|
+
|
343
|
+
# Validate configurations at initialization
|
344
|
+
self._validated_configs: Dict[
|
345
|
+
str, Optional[ServiceConfigurationBase]
|
346
|
+
] = {}
|
347
|
+
self._validate_service_configurations()
|
348
|
+
|
349
|
+
async def get_mcp_manager(self) -> Optional[MCPManagerProtocol]:
|
350
|
+
"""Get or create MCP server manager.
|
351
|
+
|
352
|
+
Returns:
|
353
|
+
Configured MCP manager instance or None if not needed
|
354
|
+
"""
|
355
|
+
if self._mcp_manager is None and self.args.get("mcp_servers"):
|
356
|
+
self._mcp_manager = await self.factory.create_mcp_manager(
|
357
|
+
self.args
|
358
|
+
)
|
359
|
+
return self._mcp_manager
|
360
|
+
|
361
|
+
async def get_code_interpreter_manager(
|
362
|
+
self,
|
363
|
+
) -> Optional[CodeInterpreterManagerProtocol]:
|
364
|
+
"""Get or create code interpreter manager.
|
365
|
+
|
366
|
+
Returns:
|
367
|
+
Configured code interpreter manager instance or None if not needed
|
368
|
+
"""
|
369
|
+
if self._code_interpreter_manager is None and self.args.get(
|
370
|
+
"code_interpreter"
|
371
|
+
):
|
372
|
+
self._code_interpreter_manager = (
|
373
|
+
await self.factory.create_code_interpreter_manager(
|
374
|
+
self.args, self.client
|
375
|
+
)
|
376
|
+
)
|
377
|
+
return self._code_interpreter_manager
|
378
|
+
|
379
|
+
async def get_file_search_manager(
|
380
|
+
self,
|
381
|
+
) -> Optional[FileSearchManagerProtocol]:
|
382
|
+
"""Get or create file search manager.
|
383
|
+
|
384
|
+
Returns:
|
385
|
+
Configured file search manager instance or None if not needed
|
386
|
+
"""
|
387
|
+
if self._file_search_manager is None and self.args.get("file_search"):
|
388
|
+
self._file_search_manager = (
|
389
|
+
await self.factory.create_file_search_manager(
|
390
|
+
self.args, self.client
|
391
|
+
)
|
392
|
+
)
|
393
|
+
return self._file_search_manager
|
394
|
+
|
395
|
+
async def cleanup(self) -> None:
|
396
|
+
"""Clean up all managed services."""
|
397
|
+
cleanup_tasks = []
|
398
|
+
|
399
|
+
# Clean up MCP manager if it exists
|
400
|
+
if self._mcp_manager:
|
401
|
+
# MCPServerManager doesn't have a cleanup method, skip it
|
402
|
+
pass
|
403
|
+
|
404
|
+
# Clean up code interpreter manager
|
405
|
+
if self._code_interpreter_manager:
|
406
|
+
# Use the specific cleanup method for code interpreter
|
407
|
+
if hasattr(
|
408
|
+
self._code_interpreter_manager, "cleanup_uploaded_files"
|
409
|
+
):
|
410
|
+
cleanup_tasks.append(
|
411
|
+
self._code_interpreter_manager.cleanup_uploaded_files()
|
412
|
+
)
|
413
|
+
|
414
|
+
# Clean up file search manager
|
415
|
+
if self._file_search_manager:
|
416
|
+
# Use the specific cleanup method for file search
|
417
|
+
if hasattr(self._file_search_manager, "cleanup_resources"):
|
418
|
+
cleanup_tasks.append(
|
419
|
+
self._file_search_manager.cleanup_resources()
|
420
|
+
)
|
421
|
+
|
422
|
+
# Execute cleanup tasks concurrently
|
423
|
+
if cleanup_tasks:
|
424
|
+
import asyncio
|
425
|
+
|
426
|
+
await asyncio.gather(*cleanup_tasks, return_exceptions=True)
|
427
|
+
|
428
|
+
def is_configured(self, service_name: str) -> bool:
|
429
|
+
"""Check if a service is configured.
|
430
|
+
|
431
|
+
Args:
|
432
|
+
service_name: Name of the service to check
|
433
|
+
|
434
|
+
Returns:
|
435
|
+
True if the service is configured in args
|
436
|
+
"""
|
437
|
+
service_mapping = {
|
438
|
+
"mcp": "mcp_servers",
|
439
|
+
"code_interpreter": "code_interpreter",
|
440
|
+
"file_search": "file_search",
|
441
|
+
}
|
442
|
+
|
443
|
+
arg_key = service_mapping.get(service_name)
|
444
|
+
if not arg_key:
|
445
|
+
return False
|
446
|
+
|
447
|
+
return bool(self.args.get(arg_key))
|
448
|
+
|
449
|
+
def get_service_configuration(self, service_name: str) -> Any:
|
450
|
+
"""Get service configuration from args.
|
451
|
+
|
452
|
+
Args:
|
453
|
+
service_name: Name of the service
|
454
|
+
|
455
|
+
Returns:
|
456
|
+
Service configuration or None
|
457
|
+
"""
|
458
|
+
service_mapping = {
|
459
|
+
"mcp": "mcp_servers",
|
460
|
+
"code_interpreter": "code_interpreter",
|
461
|
+
"file_search": "file_search",
|
462
|
+
}
|
463
|
+
|
464
|
+
arg_key = service_mapping.get(service_name)
|
465
|
+
if not arg_key:
|
466
|
+
return None
|
467
|
+
|
468
|
+
return self.args.get(arg_key)
|
469
|
+
|
470
|
+
def _validate_service_configurations(self) -> None:
|
471
|
+
"""Validate all service configurations at container initialization."""
|
472
|
+
validator = ServiceConfigurationValidator()
|
473
|
+
|
474
|
+
# Validate MCP configuration if present
|
475
|
+
if self.args.get("mcp_servers"):
|
476
|
+
try:
|
477
|
+
mcp_config = {"servers": self.args["mcp_servers"]}
|
478
|
+
self._validated_configs["mcp"] = validator.validate_mcp_config(
|
479
|
+
mcp_config
|
480
|
+
)
|
481
|
+
logger.debug("MCP configuration validated successfully")
|
482
|
+
except (ValidationError, ValueError) as e:
|
483
|
+
logger.warning(f"MCP configuration validation failed: {e}")
|
484
|
+
self._validated_configs["mcp"] = None
|
485
|
+
|
486
|
+
# Validate Code Interpreter configuration if present
|
487
|
+
if self.args.get("code_interpreter"):
|
488
|
+
try:
|
489
|
+
ci_config = {"enabled": True} # Basic config, extend as needed
|
490
|
+
self._validated_configs["code_interpreter"] = (
|
491
|
+
validator.validate_code_interpreter_config(ci_config)
|
492
|
+
)
|
493
|
+
logger.debug(
|
494
|
+
"Code Interpreter configuration validated successfully"
|
495
|
+
)
|
496
|
+
except (ValidationError, ValueError) as e:
|
497
|
+
logger.warning(
|
498
|
+
f"Code Interpreter configuration validation failed: {e}"
|
499
|
+
)
|
500
|
+
self._validated_configs["code_interpreter"] = None
|
501
|
+
|
502
|
+
# Validate File Search configuration if present
|
503
|
+
if self.args.get("file_search"):
|
504
|
+
try:
|
505
|
+
fs_config = {"enabled": True} # Basic config, extend as needed
|
506
|
+
self._validated_configs["file_search"] = (
|
507
|
+
validator.validate_file_search_config(fs_config)
|
508
|
+
)
|
509
|
+
logger.debug(
|
510
|
+
"File Search configuration validated successfully"
|
511
|
+
)
|
512
|
+
except (ValidationError, ValueError) as e:
|
513
|
+
logger.warning(
|
514
|
+
f"File Search configuration validation failed: {e}"
|
515
|
+
)
|
516
|
+
self._validated_configs["file_search"] = None
|
517
|
+
|
518
|
+
def get_validated_config(
|
519
|
+
self, service_name: str
|
520
|
+
) -> Optional[ServiceConfigurationBase]:
|
521
|
+
"""Get validated configuration for a service.
|
522
|
+
|
523
|
+
Args:
|
524
|
+
service_name: Name of the service
|
525
|
+
|
526
|
+
Returns:
|
527
|
+
Validated configuration or None if validation failed
|
528
|
+
"""
|
529
|
+
return self._validated_configs.get(service_name)
|
530
|
+
|
531
|
+
async def check_service_health(self, service_name: str) -> ServiceHealth:
|
532
|
+
"""Check health of a specific service.
|
533
|
+
|
534
|
+
Args:
|
535
|
+
service_name: Name of the service to check
|
536
|
+
|
537
|
+
Returns:
|
538
|
+
ServiceHealth with status and details
|
539
|
+
"""
|
540
|
+
if service_name == "mcp":
|
541
|
+
mcp_manager = await self.get_mcp_manager()
|
542
|
+
if mcp_manager and hasattr(mcp_manager, "health_check"):
|
543
|
+
return await mcp_manager.health_check()
|
544
|
+
elif mcp_manager:
|
545
|
+
return ServiceHealth(
|
546
|
+
status=ServiceStatus.HEALTHY,
|
547
|
+
message="MCP manager is running",
|
548
|
+
details={"has_health_check": False},
|
549
|
+
)
|
550
|
+
elif service_name == "code_interpreter":
|
551
|
+
ci_manager = await self.get_code_interpreter_manager()
|
552
|
+
if ci_manager and hasattr(ci_manager, "health_check"):
|
553
|
+
return await ci_manager.health_check()
|
554
|
+
elif ci_manager:
|
555
|
+
return ServiceHealth(
|
556
|
+
status=ServiceStatus.HEALTHY,
|
557
|
+
message="Code Interpreter manager is running",
|
558
|
+
details={"has_health_check": False},
|
559
|
+
)
|
560
|
+
elif service_name == "file_search":
|
561
|
+
fs_manager = await self.get_file_search_manager()
|
562
|
+
if fs_manager and hasattr(fs_manager, "health_check"):
|
563
|
+
return await fs_manager.health_check()
|
564
|
+
elif fs_manager:
|
565
|
+
return ServiceHealth(
|
566
|
+
status=ServiceStatus.HEALTHY,
|
567
|
+
message="File Search manager is running",
|
568
|
+
details={"has_health_check": False},
|
569
|
+
)
|
570
|
+
|
571
|
+
# Service not configured or not found
|
572
|
+
return ServiceHealth(
|
573
|
+
status=ServiceStatus.UNKNOWN,
|
574
|
+
message=f"Service '{service_name}' not configured or not found",
|
575
|
+
)
|
576
|
+
|
577
|
+
async def check_all_services_health(self) -> Dict[str, ServiceHealth]:
|
578
|
+
"""Check health of all configured services.
|
579
|
+
|
580
|
+
Returns:
|
581
|
+
Dictionary mapping service names to their health status
|
582
|
+
"""
|
583
|
+
health_checks = {}
|
584
|
+
|
585
|
+
# Check each configured service
|
586
|
+
for service_name in ["mcp", "code_interpreter", "file_search"]:
|
587
|
+
if self.is_configured(service_name):
|
588
|
+
health_checks[service_name] = await self.check_service_health(
|
589
|
+
service_name
|
590
|
+
)
|
591
|
+
|
592
|
+
return health_checks
|
593
|
+
|
594
|
+
def get_service_info(self) -> Dict[str, Dict[str, Any]]:
|
595
|
+
"""Get information about all services.
|
596
|
+
|
597
|
+
Returns:
|
598
|
+
Dictionary with service information including configuration status
|
599
|
+
"""
|
600
|
+
service_info = {}
|
601
|
+
|
602
|
+
for service_name in ["mcp", "code_interpreter", "file_search"]:
|
603
|
+
is_configured = self.is_configured(service_name)
|
604
|
+
validated_config = self.get_validated_config(service_name)
|
605
|
+
|
606
|
+
service_info[service_name] = {
|
607
|
+
"configured": is_configured,
|
608
|
+
"validated": validated_config is not None,
|
609
|
+
"config_valid": (
|
610
|
+
validated_config is not None if is_configured else None
|
611
|
+
),
|
612
|
+
"has_manager": False, # Will be updated when managers are created
|
613
|
+
}
|
614
|
+
|
615
|
+
# Check if manager exists
|
616
|
+
if service_name == "mcp" and self._mcp_manager:
|
617
|
+
service_info[service_name]["has_manager"] = True
|
618
|
+
elif (
|
619
|
+
service_name == "code_interpreter"
|
620
|
+
and self._code_interpreter_manager
|
621
|
+
):
|
622
|
+
service_info[service_name]["has_manager"] = True
|
623
|
+
elif service_name == "file_search" and self._file_search_manager:
|
624
|
+
service_info[service_name]["has_manager"] = True
|
625
|
+
|
626
|
+
return service_info
|