2026-02-28 13:36:30 +08:00
|
|
|
typedef ToolHandler =
|
|
|
|
|
Future<Map<String, dynamic>> Function(Map<String, dynamic> args);
|
|
|
|
|
|
2026-02-28 14:41:21 +08:00
|
|
|
/// 工具常量
|
|
|
|
|
const _toolNameCreateCalendar = 'create_calendar_event';
|
|
|
|
|
const _defaultTimezone = 'Asia/Shanghai';
|
|
|
|
|
const _defaultEventColor = '#4F46E5';
|
|
|
|
|
const _defaultSourceType = 'agentGenerated';
|
|
|
|
|
const _titleMinLength = 1;
|
|
|
|
|
const _titleMaxLength = 100;
|
|
|
|
|
|
2026-02-28 13:36:30 +08:00
|
|
|
class ToolDefinition {
|
|
|
|
|
final String name;
|
|
|
|
|
final String description;
|
|
|
|
|
final Map<String, dynamic> parameters;
|
|
|
|
|
final ToolHandler handler;
|
|
|
|
|
|
|
|
|
|
ToolDefinition({
|
|
|
|
|
required this.name,
|
|
|
|
|
required this.description,
|
|
|
|
|
required this.parameters,
|
|
|
|
|
required this.handler,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class ToolRegistry {
|
|
|
|
|
static final Map<String, ToolDefinition> _tools = {};
|
|
|
|
|
static bool _initialized = false;
|
|
|
|
|
|
|
|
|
|
static void initialize() {
|
|
|
|
|
if (_initialized) return;
|
|
|
|
|
|
2026-02-28 14:41:21 +08:00
|
|
|
_tools[_toolNameCreateCalendar] = ToolDefinition(
|
|
|
|
|
name: _toolNameCreateCalendar,
|
2026-02-28 13:36:30 +08:00
|
|
|
description: '创建一个日历事件或待办事项',
|
|
|
|
|
parameters: {
|
|
|
|
|
'type': 'object',
|
|
|
|
|
'properties': {
|
|
|
|
|
'title': {
|
|
|
|
|
'type': 'string',
|
|
|
|
|
'description': '事件标题',
|
2026-02-28 14:41:21 +08:00
|
|
|
'minLength': _titleMinLength,
|
|
|
|
|
'maxLength': _titleMaxLength,
|
2026-02-28 13:36:30 +08:00
|
|
|
},
|
|
|
|
|
'description': {'type': 'string', 'description': '事件描述'},
|
|
|
|
|
'startAt': {
|
|
|
|
|
'type': 'string',
|
|
|
|
|
'format': 'date-time',
|
|
|
|
|
'description': '开始时间 (ISO8601)',
|
|
|
|
|
},
|
|
|
|
|
'endAt': {
|
|
|
|
|
'type': 'string',
|
|
|
|
|
'format': 'date-time',
|
|
|
|
|
'description': '结束时间 (ISO8601)',
|
|
|
|
|
},
|
2026-02-28 14:41:21 +08:00
|
|
|
'timezone': {'type': 'string', 'default': _defaultTimezone},
|
2026-02-28 13:36:30 +08:00
|
|
|
'location': {'type': 'string'},
|
|
|
|
|
'notes': {'type': 'string'},
|
|
|
|
|
},
|
|
|
|
|
'required': ['title', 'startAt'],
|
|
|
|
|
},
|
|
|
|
|
handler: _handleCreateCalendarEvent,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
_initialized = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static Future<Map<String, dynamic>> _handleCreateCalendarEvent(
|
|
|
|
|
Map<String, dynamic> args,
|
|
|
|
|
) async {
|
|
|
|
|
final eventId = 'evt_${DateTime.now().millisecondsSinceEpoch}';
|
|
|
|
|
return {
|
|
|
|
|
'eventId': eventId,
|
|
|
|
|
'ok': true,
|
|
|
|
|
'message': '日程已创建',
|
|
|
|
|
'title': args['title'],
|
|
|
|
|
'description': args['description'],
|
|
|
|
|
'startAt': args['startAt'],
|
|
|
|
|
'endAt': args['endAt'],
|
2026-02-28 14:41:21 +08:00
|
|
|
'timezone': args['timezone'] ?? _defaultTimezone,
|
2026-02-28 13:36:30 +08:00
|
|
|
'location': args['location'],
|
2026-02-28 14:41:21 +08:00
|
|
|
'color': _defaultEventColor,
|
|
|
|
|
'sourceType': _defaultSourceType,
|
2026-02-28 13:36:30 +08:00
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static ToolDefinition? getTool(String name) => _tools[name];
|
|
|
|
|
static List<ToolDefinition> getAllTools() => _tools.values.toList();
|
|
|
|
|
|
|
|
|
|
static Future<Map<String, dynamic>> execute(
|
|
|
|
|
String toolName,
|
|
|
|
|
Map<String, dynamic> args,
|
|
|
|
|
) async {
|
|
|
|
|
final tool = _tools[toolName];
|
|
|
|
|
if (tool == null) throw ToolNotFoundException('Tool not found: $toolName');
|
|
|
|
|
return tool.handler(args);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static ToolValidationResult validateArgs(
|
|
|
|
|
String toolName,
|
|
|
|
|
Map<String, dynamic> args,
|
|
|
|
|
) {
|
|
|
|
|
final tool = _tools[toolName];
|
2026-02-28 14:41:21 +08:00
|
|
|
if (tool == null) {
|
2026-02-28 13:36:30 +08:00
|
|
|
return ToolValidationResult(
|
|
|
|
|
ok: false,
|
|
|
|
|
error: 'Tool not found: $toolName',
|
|
|
|
|
);
|
2026-02-28 14:41:21 +08:00
|
|
|
}
|
2026-02-28 13:36:30 +08:00
|
|
|
|
|
|
|
|
final required = tool.parameters['required'] as List<dynamic>? ?? [];
|
|
|
|
|
final missing = <String>[];
|
|
|
|
|
for (final field in required) {
|
2026-02-28 14:41:21 +08:00
|
|
|
if (!args.containsKey(field) || args[field] == null) {
|
2026-02-28 13:36:30 +08:00
|
|
|
missing.add(field as String);
|
2026-02-28 14:41:21 +08:00
|
|
|
}
|
2026-02-28 13:36:30 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (missing.isNotEmpty) {
|
|
|
|
|
return ToolValidationResult(
|
|
|
|
|
ok: false,
|
|
|
|
|
error: 'Missing required fields: ${missing.join(', ')}',
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
return ToolValidationResult(ok: true);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class ToolNotFoundException implements Exception {
|
|
|
|
|
final String message;
|
|
|
|
|
ToolNotFoundException(this.message);
|
|
|
|
|
@override
|
|
|
|
|
String toString() => 'ToolNotFoundException: $message';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class ToolValidationResult {
|
|
|
|
|
final bool ok;
|
|
|
|
|
final String? error;
|
|
|
|
|
ToolValidationResult({required this.ok, this.error});
|
|
|
|
|
}
|