390 lines
10 KiB
Dart
390 lines
10 KiB
Dart
// Agent registry for OpenClaw Gateway integration.
|
|
//
|
|
// This module handles agent registration and discovery through the Gateway.
|
|
|
|
import 'dart:async';
|
|
|
|
import 'package:flutter/foundation.dart';
|
|
|
|
import 'gateway_runtime.dart';
|
|
|
|
/// Agent capability description.
|
|
class AgentCapability {
|
|
final String name;
|
|
final String description;
|
|
final Map<String, dynamic>? parameters;
|
|
|
|
const AgentCapability({
|
|
required this.name,
|
|
required this.description,
|
|
this.parameters,
|
|
});
|
|
|
|
factory AgentCapability.fromJson(Map<String, dynamic> json) {
|
|
return AgentCapability(
|
|
name: json['name'] as String,
|
|
description: json['description'] as String,
|
|
parameters: json['parameters'] as Map<String, dynamic>?,
|
|
);
|
|
}
|
|
|
|
Map<String, dynamic> toJson() => {
|
|
'name': name,
|
|
'description': description,
|
|
if (parameters != null) 'parameters': parameters,
|
|
};
|
|
}
|
|
|
|
/// Agent registration information.
|
|
class AgentRegistration {
|
|
final String agentId;
|
|
final String agentType;
|
|
final String name;
|
|
final String version;
|
|
final String token;
|
|
final DateTime registeredAt;
|
|
final DateTime? expiresAt;
|
|
final List<AgentCapability> capabilities;
|
|
|
|
const AgentRegistration({
|
|
required this.agentId,
|
|
required this.agentType,
|
|
required this.name,
|
|
required this.version,
|
|
required this.token,
|
|
required this.registeredAt,
|
|
this.expiresAt,
|
|
this.capabilities = const [],
|
|
});
|
|
|
|
factory AgentRegistration.fromJson(Map<String, dynamic> json) {
|
|
return AgentRegistration(
|
|
agentId: json['agentId'] as String,
|
|
agentType: json['agentType'] as String,
|
|
name: json['name'] as String,
|
|
version: json['version'] as String,
|
|
token: json['token'] as String,
|
|
registeredAt: DateTime.parse(json['registeredAt'] as String),
|
|
expiresAt: json['expiresAt'] != null
|
|
? DateTime.tryParse(json['expiresAt'] as String)
|
|
: null,
|
|
capabilities:
|
|
(json['capabilities'] as List?)
|
|
?.map((e) => AgentCapability.fromJson(e as Map<String, dynamic>))
|
|
.toList() ??
|
|
[],
|
|
);
|
|
}
|
|
|
|
Map<String, dynamic> toJson() => {
|
|
'agentId': agentId,
|
|
'agentType': agentType,
|
|
'name': name,
|
|
'version': version,
|
|
'token': token,
|
|
'registeredAt': registeredAt.toIso8601String(),
|
|
if (expiresAt != null) 'expiresAt': expiresAt!.toIso8601String(),
|
|
'capabilities': capabilities.map((c) => c.toJson()).toList(),
|
|
};
|
|
}
|
|
|
|
/// Agent information from registry.
|
|
class AgentInfo {
|
|
final String agentId;
|
|
final String agentType;
|
|
final String name;
|
|
final String status;
|
|
final List<String> capabilities;
|
|
final bool isOnline;
|
|
final DateTime? lastSeen;
|
|
|
|
const AgentInfo({
|
|
required this.agentId,
|
|
required this.agentType,
|
|
required this.name,
|
|
required this.status,
|
|
this.capabilities = const [],
|
|
this.isOnline = false,
|
|
this.lastSeen,
|
|
});
|
|
|
|
factory AgentInfo.fromJson(Map<String, dynamic> json) {
|
|
return AgentInfo(
|
|
agentId: json['agentId'] as String,
|
|
agentType: json['agentType'] as String,
|
|
name: json['name'] as String,
|
|
status: json['status'] as String,
|
|
capabilities:
|
|
(json['capabilities'] as List?)?.map((e) => e as String).toList() ??
|
|
[],
|
|
isOnline: json['isOnline'] as bool? ?? false,
|
|
lastSeen: json['lastSeen'] != null
|
|
? DateTime.tryParse(json['lastSeen'] as String)
|
|
: null,
|
|
);
|
|
}
|
|
}
|
|
|
|
/// Agent response from invoke.
|
|
class AgentResponse {
|
|
final String content;
|
|
final String? threadId;
|
|
final String? turnId;
|
|
final Map<String, dynamic>? metadata;
|
|
|
|
const AgentResponse({
|
|
required this.content,
|
|
this.threadId,
|
|
this.turnId,
|
|
this.metadata,
|
|
});
|
|
|
|
factory AgentResponse.fromJson(Map<String, dynamic> json) {
|
|
return AgentResponse(
|
|
content: json['content'] as String? ?? '',
|
|
threadId: json['threadId'] as String?,
|
|
turnId: json['turnId'] as String?,
|
|
metadata: json['metadata'] as Map<String, dynamic>?,
|
|
);
|
|
}
|
|
}
|
|
|
|
/// Exception for agent operations.
|
|
class AgentException implements Exception {
|
|
final String message;
|
|
final String? code;
|
|
|
|
const AgentException(this.message, {this.code});
|
|
|
|
@override
|
|
String toString() =>
|
|
code != null ? 'AgentException($code): $message' : message;
|
|
}
|
|
|
|
/// Agent registry for managing agent registration and discovery.
|
|
class AgentRegistry with ChangeNotifier {
|
|
final GatewayRuntime _gateway;
|
|
|
|
AgentRegistration? _registration;
|
|
List<AgentInfo> _agents = [];
|
|
String? _lastError;
|
|
bool _isRegistering = false;
|
|
|
|
AgentRegistry(this._gateway);
|
|
|
|
AgentRegistration? get registration => _registration;
|
|
List<AgentInfo> get agents => List.unmodifiable(_agents);
|
|
String? get lastError => _lastError;
|
|
bool get isRegistered => _registration != null;
|
|
bool get isRegistering => _isRegistering;
|
|
|
|
/// Register this agent with the Gateway.
|
|
Future<AgentRegistration> register({
|
|
required String agentType,
|
|
required String name,
|
|
required String version,
|
|
required List<AgentCapability> capabilities,
|
|
String transport = 'in-process',
|
|
Map<String, dynamic>? metadata,
|
|
}) async {
|
|
if (!_gateway.isConnected) {
|
|
throw AgentException('Gateway not connected', code: 'NOT_CONNECTED');
|
|
}
|
|
|
|
_isRegistering = true;
|
|
_lastError = null;
|
|
notifyListeners();
|
|
|
|
try {
|
|
final response = await _gateway.request(
|
|
'agent/register',
|
|
params: {
|
|
'agentType': agentType,
|
|
'name': name,
|
|
'version': version,
|
|
'capabilities': capabilities.map((c) => c.toJson()).toList(),
|
|
...?metadata == null ? null : <String, dynamic>{'metadata': metadata},
|
|
'transport': transport,
|
|
},
|
|
);
|
|
|
|
_registration = AgentRegistration.fromJson(
|
|
response as Map<String, dynamic>,
|
|
);
|
|
notifyListeners();
|
|
return _registration!;
|
|
} catch (e) {
|
|
_lastError = e.toString();
|
|
notifyListeners();
|
|
throw AgentException(
|
|
'Failed to register agent: $e',
|
|
code: 'REGISTRATION_FAILED',
|
|
);
|
|
} finally {
|
|
_isRegistering = false;
|
|
notifyListeners();
|
|
}
|
|
}
|
|
|
|
/// Unregister this agent from the Gateway.
|
|
Future<void> unregister() async {
|
|
if (_registration == null) {
|
|
return;
|
|
}
|
|
|
|
if (!_gateway.isConnected) {
|
|
throw AgentException('Gateway not connected', code: 'NOT_CONNECTED');
|
|
}
|
|
|
|
try {
|
|
await _gateway.request(
|
|
'agent/unregister',
|
|
params: {'agentId': _registration!.agentId},
|
|
);
|
|
|
|
_registration = null;
|
|
notifyListeners();
|
|
} catch (e) {
|
|
_lastError = e.toString();
|
|
notifyListeners();
|
|
throw AgentException(
|
|
'Failed to unregister agent: $e',
|
|
code: 'UNREGISTRATION_FAILED',
|
|
);
|
|
}
|
|
}
|
|
|
|
/// Clear local registration state without calling the Gateway.
|
|
void clearRegistration() {
|
|
_registration = null;
|
|
notifyListeners();
|
|
}
|
|
|
|
/// List all registered agents.
|
|
Future<List<AgentInfo>> listAgents({String? agentType}) async {
|
|
if (!_gateway.isConnected) {
|
|
throw AgentException('Gateway not connected', code: 'NOT_CONNECTED');
|
|
}
|
|
|
|
try {
|
|
final response = await _gateway.request(
|
|
'agent/list',
|
|
params: <String, dynamic>{
|
|
...?agentType == null
|
|
? null
|
|
: <String, dynamic>{'agentType': agentType},
|
|
},
|
|
);
|
|
|
|
final agentsJson = response['agents'] as List? ?? [];
|
|
_agents = agentsJson
|
|
.map((a) => AgentInfo.fromJson(a as Map<String, dynamic>))
|
|
.toList();
|
|
notifyListeners();
|
|
return _agents;
|
|
} catch (e) {
|
|
_lastError = e.toString();
|
|
notifyListeners();
|
|
throw AgentException('Failed to list agents: $e', code: 'LIST_FAILED');
|
|
}
|
|
}
|
|
|
|
/// Invoke a remote agent.
|
|
Future<AgentResponse> invokeAgent({
|
|
required String agentId,
|
|
required String prompt,
|
|
Map<String, dynamic>? context,
|
|
String? threadId,
|
|
}) async {
|
|
if (!_gateway.isConnected) {
|
|
throw AgentException('Gateway not connected', code: 'NOT_CONNECTED');
|
|
}
|
|
|
|
try {
|
|
final response = await _gateway.request(
|
|
'agent/invoke',
|
|
params: {
|
|
'agentId': agentId,
|
|
'prompt': prompt,
|
|
...?context == null ? null : <String, dynamic>{'context': context},
|
|
...?threadId == null ? null : <String, dynamic>{'threadId': threadId},
|
|
},
|
|
);
|
|
|
|
return AgentResponse.fromJson(response as Map<String, dynamic>);
|
|
} catch (e) {
|
|
_lastError = e.toString();
|
|
notifyListeners();
|
|
throw AgentException('Failed to invoke agent: $e', code: 'INVOKE_FAILED');
|
|
}
|
|
}
|
|
|
|
/// Update agent status.
|
|
Future<void> updateStatus({
|
|
required String status,
|
|
List<String>? capabilities,
|
|
}) async {
|
|
if (_registration == null) {
|
|
throw AgentException('Agent not registered', code: 'NOT_REGISTERED');
|
|
}
|
|
|
|
if (!_gateway.isConnected) {
|
|
throw AgentException('Gateway not connected', code: 'NOT_CONNECTED');
|
|
}
|
|
|
|
try {
|
|
await _gateway.request(
|
|
'agent/updateStatus',
|
|
params: {
|
|
'agentId': _registration!.agentId,
|
|
'status': status,
|
|
...?capabilities == null
|
|
? null
|
|
: <String, dynamic>{'capabilities': capabilities},
|
|
},
|
|
);
|
|
} catch (e) {
|
|
_lastError = e.toString();
|
|
notifyListeners();
|
|
throw AgentException(
|
|
'Failed to update status: $e',
|
|
code: 'UPDATE_FAILED',
|
|
);
|
|
}
|
|
}
|
|
|
|
/// Sync memory with cloud.
|
|
Future<Map<String, dynamic>> syncMemory({
|
|
required String direction,
|
|
String? sinceVersion,
|
|
}) async {
|
|
if (!_gateway.isConnected) {
|
|
throw AgentException('Gateway not connected', code: 'NOT_CONNECTED');
|
|
}
|
|
|
|
try {
|
|
final response = await _gateway.request(
|
|
'memory/sync',
|
|
params: {
|
|
'direction': direction, // 'pull', 'push', 'both'
|
|
...?sinceVersion == null
|
|
? null
|
|
: <String, dynamic>{'sinceVersion': sinceVersion},
|
|
},
|
|
);
|
|
|
|
return response as Map<String, dynamic>;
|
|
} catch (e) {
|
|
_lastError = e.toString();
|
|
notifyListeners();
|
|
throw AgentException('Failed to sync memory: $e', code: 'SYNC_FAILED');
|
|
}
|
|
}
|
|
|
|
/// Clear last error.
|
|
void clearError() {
|
|
_lastError = null;
|
|
notifyListeners();
|
|
}
|
|
}
|