/// Agent management endpoints for CQRS API library; import 'dart:async'; import 'dart:convert'; import 'dart:io'; import 'package:http/http.dart' as http; import '../client.dart'; import '../types.dart'; // ============================================================================= // Enums // ============================================================================= /// Specifies the type/purpose of the agent enum AgentType { codeGenerator('CodeGenerator'), codeReviewer('CodeReviewer'), debugger('Debugger'), documenter('Documenter'), custom('Custom'); const AgentType(this.value); final String value; static AgentType fromString(String value) { return AgentType.values.firstWhere( (type) => type.value == value, orElse: () => AgentType.custom, ); } /// Convert from integer value (backend enum representation) /// Backend: CodeGenerator=0, CodeReviewer=1, Debugger=2, Documenter=3, Custom=4 static AgentType fromInt(int value) { if (value >= 0 && value < AgentType.values.length) { return AgentType.values[value]; } return AgentType.custom; } } /// Represents the current status of an agent enum AgentStatus { active('Active'), inactive('Inactive'), error('Error'); const AgentStatus(this.value); final String value; static AgentStatus fromString(String value) { return AgentStatus.values.firstWhere( (status) => status.value == value, orElse: () => AgentStatus.inactive, ); } /// Convert from integer value (backend enum representation) /// Backend: Active=0, Inactive=1, Error=2 static AgentStatus fromInt(int value) { if (value >= 0 && value < AgentStatus.values.length) { return AgentStatus.values[value]; } return AgentStatus.inactive; } } /// Specifies the type of model provider (cloud API or local endpoint) enum ModelProviderType { cloudApi('CloudApi'), localEndpoint('LocalEndpoint'), custom('Custom'); const ModelProviderType(this.value); final String value; static ModelProviderType fromString(String value) { return ModelProviderType.values.firstWhere( (type) => type.value == value, orElse: () => ModelProviderType.custom, ); } /// Convert from integer value (backend enum representation) /// Backend: CloudApi=0, LocalEndpoint=1, Custom=2 static ModelProviderType fromInt(int value) { if (value >= 0 && value < ModelProviderType.values.length) { return ModelProviderType.values[value]; } return ModelProviderType.custom; } } // ============================================================================= // Commands // ============================================================================= /// Command to create a new AI agent with configuration class CreateAgentCommand implements Serializable { final String name; final String description; final AgentType type; final String modelProvider; final String modelName; final ModelProviderType providerType; final String? modelEndpoint; final String? apiKey; final double temperature; final int maxTokens; final String systemPrompt; final bool enableMemory; final int conversationWindowSize; const CreateAgentCommand({ required this.name, required this.description, required this.type, required this.modelProvider, required this.modelName, required this.providerType, this.modelEndpoint, this.apiKey, this.temperature = 0.7, this.maxTokens = 4000, required this.systemPrompt, this.enableMemory = true, this.conversationWindowSize = 10, }); @override Map toJson() => { 'name': name, 'description': description, 'type': type.value, 'modelProvider': modelProvider, 'modelName': modelName, 'providerType': providerType.value, 'modelEndpoint': modelEndpoint, 'apiKey': apiKey, 'temperature': temperature, 'maxTokens': maxTokens, 'systemPrompt': systemPrompt, 'enableMemory': enableMemory, 'conversationWindowSize': conversationWindowSize, }; } /// Command to update an existing agent's configuration class UpdateAgentCommand implements Serializable { final String id; final String? name; final String? description; final AgentType? type; final String? modelProvider; final String? modelName; final ModelProviderType? providerType; final String? modelEndpoint; final String? apiKey; final double? temperature; final int? maxTokens; final String? systemPrompt; final bool? enableMemory; final int? conversationWindowSize; final AgentStatus? status; const UpdateAgentCommand({ required this.id, this.name, this.description, this.type, this.modelProvider, this.modelName, this.providerType, this.modelEndpoint, this.apiKey, this.temperature, this.maxTokens, this.systemPrompt, this.enableMemory, this.conversationWindowSize, this.status, }); @override Map toJson() => { 'id': id, if (name != null) 'name': name, if (description != null) 'description': description, if (type != null) 'type': type!.value, if (modelProvider != null) 'modelProvider': modelProvider, if (modelName != null) 'modelName': modelName, if (providerType != null) 'providerType': providerType!.value, if (modelEndpoint != null) 'modelEndpoint': modelEndpoint, if (apiKey != null) 'apiKey': apiKey, if (temperature != null) 'temperature': temperature, if (maxTokens != null) 'maxTokens': maxTokens, if (systemPrompt != null) 'systemPrompt': systemPrompt, if (enableMemory != null) 'enableMemory': enableMemory, if (conversationWindowSize != null) 'conversationWindowSize': conversationWindowSize, if (status != null) 'status': status!.value, }; } /// Command to soft-delete an agent class DeleteAgentCommand implements Serializable { final String id; const DeleteAgentCommand({required this.id}); @override Map toJson() => {'id': id}; } // ============================================================================= // Queries // ============================================================================= /// Query to get a single agent by ID class GetAgentQuery implements Serializable { final String id; const GetAgentQuery({required this.id}); @override Map toJson() => {'id': id}; } // ============================================================================= // DTOs // ============================================================================= /// Response containing agent details class AgentDto { final String id; final String name; final String description; final AgentType type; final String modelProvider; final String modelName; final ModelProviderType providerType; final String? modelEndpoint; final double? temperature; final int? maxTokens; final String? systemPrompt; final bool? enableMemory; final int? conversationWindowSize; final AgentStatus status; final DateTime createdAt; final DateTime updatedAt; const AgentDto({ required this.id, required this.name, required this.description, required this.type, required this.modelProvider, required this.modelName, required this.providerType, this.modelEndpoint, this.temperature, this.maxTokens, this.systemPrompt, this.enableMemory, this.conversationWindowSize, required this.status, required this.createdAt, required this.updatedAt, }); factory AgentDto.fromJson(Map json) { // Helper to parse enum from either int or string AgentType parseAgentType(Object? value) { if (value is int) return AgentType.fromInt(value); if (value is String) return AgentType.fromString(value); return AgentType.custom; } AgentStatus parseAgentStatus(Object? value) { if (value is int) return AgentStatus.fromInt(value); if (value is String) return AgentStatus.fromString(value); return AgentStatus.inactive; } ModelProviderType parseProviderType(Object? value) { if (value is int) return ModelProviderType.fromInt(value); if (value is String) return ModelProviderType.fromString(value); return ModelProviderType.custom; } return AgentDto( id: json['id'] as String, name: json['name'] as String, description: json['description'] as String, type: parseAgentType(json['type']), modelProvider: json['modelProvider'] as String, modelName: json['modelName'] as String, providerType: parseProviderType(json['providerType']), modelEndpoint: json['modelEndpoint'] as String?, temperature: json['temperature'] != null ? (json['temperature'] as num).toDouble() : null, maxTokens: json['maxTokens'] as int?, systemPrompt: json['systemPrompt'] as String?, enableMemory: json['enableMemory'] as bool?, conversationWindowSize: json['conversationWindowSize'] as int?, status: parseAgentStatus(json['status']), createdAt: DateTime.parse(json['createdAt'] as String), updatedAt: DateTime.parse(json['updatedAt'] as String), ); } Map toJson() => { 'id': id, 'name': name, 'description': description, 'type': type.value, 'modelProvider': modelProvider, 'modelName': modelName, 'providerType': providerType.value, 'modelEndpoint': modelEndpoint, 'temperature': temperature, 'maxTokens': maxTokens, 'systemPrompt': systemPrompt, 'enableMemory': enableMemory, 'conversationWindowSize': conversationWindowSize, 'status': status.value, 'createdAt': createdAt.toIso8601String(), 'updatedAt': updatedAt.toIso8601String(), }; } // ============================================================================= // Extension Methods // ============================================================================= /// Agent management endpoints extension AgentEndpoint on CqrsApiClient { /// Create a new AI agent /// /// Example: /// ```dart /// final result = await client.createAgent( /// CreateAgentCommand( /// name: 'Code Generator', /// description: 'AI agent for code generation', /// type: AgentType.codeGenerator, /// modelProvider: 'ollama', /// modelName: 'phi', /// providerType: ModelProviderType.localEndpoint, /// modelEndpoint: 'http://localhost:11434', /// systemPrompt: 'You are a code generation assistant', /// ), /// ); /// ``` Future> createAgent(CreateAgentCommand command) async { return executeCommand( endpoint: 'createAgent', command: command, ); } /// Update an existing agent's configuration /// /// Example: /// ```dart /// final result = await client.updateAgent( /// UpdateAgentCommand( /// id: 'agent-uuid', /// name: 'Updated Name', /// status: AgentStatus.active, /// ), /// ); /// ``` Future> updateAgent(UpdateAgentCommand command) async { return executeCommand( endpoint: 'updateAgent', command: command, ); } /// Soft-delete an agent /// /// Example: /// ```dart /// final result = await client.deleteAgent( /// DeleteAgentCommand(id: 'agent-uuid'), /// ); /// ``` Future> deleteAgent(DeleteAgentCommand command) async { return executeCommand( endpoint: 'deleteAgent', command: command, ); } /// Get a single agent by ID /// /// Example: /// ```dart /// final result = await client.getAgent('agent-uuid'); /// /// result.when( /// success: (agent) => print('Agent: ${agent.name}'), /// error: (error) => print('Error: ${error.message}'), /// ); /// ``` Future> getAgent(String id) async { return executeQuery( endpoint: 'getAgent', query: GetAgentQuery(id: id), fromJson: (json) => AgentDto.fromJson(json as Map), ); } /// List all agents /// /// Returns a list of all active agents from the backend. /// Backend endpoint: GET /api/agents /// /// Example: /// ```dart /// final result = await client.listAgents(); /// /// result.when( /// success: (agents) => print('Found ${agents.length} agents'), /// error: (error) => print('Error: ${error.message}'), /// ); /// ``` Future>> listAgents() async { try { final Uri url = Uri.parse('${config.baseUrl}/api/agents'); final http.Response response = await http .get(url, headers: config.defaultHeaders) .timeout(config.timeout); if (response.statusCode >= 200 && response.statusCode < 300) { final Object? jsonData = jsonDecode(response.body); if (jsonData is! List) { return ApiError>(ApiErrorInfo( message: 'Expected array response, got ${jsonData.runtimeType}', type: ApiErrorType.serialization, )); } final List agents = jsonData .map((item) => AgentDto.fromJson(item as Map)) .toList(); return ApiSuccess>(agents); } // Handle error responses return ApiError>(ApiErrorInfo( message: 'Failed to load agents', type: ApiErrorType.http, statusCode: response.statusCode, )); } on TimeoutException { return ApiError>(ApiErrorInfo( message: 'Request timed out', type: ApiErrorType.timeout, )); } on SocketException { return ApiError>(ApiErrorInfo( message: 'No internet connection', type: ApiErrorType.network, )); } catch (e) { return ApiError>(ApiErrorInfo( message: 'Unexpected error: $e', type: ApiErrorType.unknown, )); } } /// Get conversations for a specific agent /// /// Returns all conversations associated with the specified agent. /// Backend endpoint: GET /api/agents/{id}/conversations /// /// Example: /// ```dart /// final result = await client.getAgentConversations('agent-uuid'); /// /// result.when( /// success: (conversations) { /// print('Found ${conversations.length} conversations for agent'); /// }, /// error: (error) => print('Error: ${error.message}'), /// ); /// ``` Future>> getAgentConversations(String agentId) async { try { final Uri url = Uri.parse('${config.baseUrl}/api/agents/$agentId/conversations'); final http.Response response = await http .get(url, headers: config.defaultHeaders) .timeout(config.timeout); if (response.statusCode >= 200 && response.statusCode < 300) { final Object? jsonData = jsonDecode(response.body); if (jsonData is! List) { return ApiError>(ApiErrorInfo( message: 'Expected array response, got ${jsonData.runtimeType}', type: ApiErrorType.serialization, )); } return ApiSuccess>(jsonData); } return ApiError>(ApiErrorInfo( message: 'Failed to load agent conversations', type: ApiErrorType.http, statusCode: response.statusCode, )); } on TimeoutException { return ApiError>(ApiErrorInfo( message: 'Request timed out', type: ApiErrorType.timeout, )); } on SocketException { return ApiError>(ApiErrorInfo( message: 'No internet connection', type: ApiErrorType.network, )); } catch (e) { return ApiError>(ApiErrorInfo( message: 'Unexpected error: $e', type: ApiErrorType.unknown, )); } } /// Get execution history for a specific agent /// /// Returns the 100 most recent executions for the specified agent. /// Backend endpoint: GET /api/agents/{id}/executions /// /// Example: /// ```dart /// final result = await client.getAgentExecutions('agent-uuid'); /// /// result.when( /// success: (executions) { /// print('Found ${executions.length} executions for agent'); /// }, /// error: (error) => print('Error: ${error.message}'), /// ); /// ``` Future>> getAgentExecutions(String agentId) async { try { final Uri url = Uri.parse('${config.baseUrl}/api/agents/$agentId/executions'); final http.Response response = await http .get(url, headers: config.defaultHeaders) .timeout(config.timeout); if (response.statusCode >= 200 && response.statusCode < 300) { final Object? jsonData = jsonDecode(response.body); if (jsonData is! List) { return ApiError>(ApiErrorInfo( message: 'Expected array response, got ${jsonData.runtimeType}', type: ApiErrorType.serialization, )); } return ApiSuccess>(jsonData); } return ApiError>(ApiErrorInfo( message: 'Failed to load agent executions', type: ApiErrorType.http, statusCode: response.statusCode, )); } on TimeoutException { return ApiError>(ApiErrorInfo( message: 'Request timed out', type: ApiErrorType.timeout, )); } on SocketException { return ApiError>(ApiErrorInfo( message: 'No internet connection', type: ApiErrorType.network, )); } catch (e) { return ApiError>(ApiErrorInfo( message: 'Unexpected error: $e', type: ApiErrorType.unknown, )); } } }