int _parseInt(dynamic v, {required String field, required int fallback}) { if (v == null) { return fallback; } if (v is int) { return v; } if (v is String) { return int.tryParse(v) ?? (throw FormatException('$field invalid')); } throw FormatException('$field invalid type'); } List _parseStringList(dynamic v, {required String field}) { if (v == null) { return const []; } if (v is List && v.every((e) => e is String)) { return v.cast(); } throw FormatException('$field invalid type'); } String _parseString( dynamic v, { required String field, required String fallback, }) { if (v == null) { return fallback; } if (v is String) { return v; } throw FormatException('$field invalid type'); } class MessageContextConfigModel { final String source; final String windowMode; final int windowCount; MessageContextConfigModel({ required this.source, required this.windowMode, required this.windowCount, }); factory MessageContextConfigModel.fromJson(Map? json) { if (json == null) { return MessageContextConfigModel( source: 'latest_chat', windowMode: 'day', windowCount: 2, ); } return MessageContextConfigModel( source: _parseString( json['source'], field: 'source', fallback: 'latest_chat', ), windowMode: _parseString( json['window_mode'], field: 'window_mode', fallback: 'day', ), windowCount: _parseInt( json['window_count'], field: 'window_count', fallback: 2, ), ); } Map toJson() => { 'source': source, 'window_mode': windowMode, 'window_count': windowCount, }; MessageContextConfigModel copyWith({ String? source, String? windowMode, int? windowCount, }) { return MessageContextConfigModel( source: source ?? this.source, windowMode: windowMode ?? this.windowMode, windowCount: windowCount ?? this.windowCount, ); } } class ScheduleRunAtModel { final int hour; final int minute; ScheduleRunAtModel({required this.hour, required this.minute}); factory ScheduleRunAtModel.fromJson(Map? json) { if (json == null) { return ScheduleRunAtModel(hour: 8, minute: 0); } return ScheduleRunAtModel( hour: _parseInt(json['hour'], field: 'hour', fallback: 8), minute: _parseInt(json['minute'], field: 'minute', fallback: 0), ); } Map toJson() => {'hour': hour, 'minute': minute}; ScheduleRunAtModel copyWith({int? hour, int? minute}) { return ScheduleRunAtModel( hour: hour ?? this.hour, minute: minute ?? this.minute, ); } } class ScheduleConfigModel { final String type; final ScheduleRunAtModel runAt; final List? weekdays; ScheduleConfigModel({required this.type, required this.runAt, this.weekdays}); factory ScheduleConfigModel.fromJson(Map? json) { if (json == null) { return ScheduleConfigModel( type: 'daily', runAt: ScheduleRunAtModel(hour: 8, minute: 0), ); } final type = _parseString(json['type'], field: 'type', fallback: 'daily'); final dynamic weekdaysRaw = json['weekdays']; List? weekdays; if (weekdaysRaw is List) { weekdays = weekdaysRaw .map((item) => _parseInt(item, field: 'weekdays', fallback: 1)) .toList(); } return ScheduleConfigModel( type: type, runAt: ScheduleRunAtModel.fromJson( json['run_at'] as Map?, ), weekdays: type.toLowerCase() == 'weekly' ? weekdays ?? [1] : null, ); } Map toJson() { final map = {'type': type, 'run_at': runAt.toJson()}; if (weekdays != null) { map['weekdays'] = weekdays; } return map; } ScheduleConfigModel copyWith({ String? type, ScheduleRunAtModel? runAt, List? weekdays, }) { return ScheduleConfigModel( type: type ?? this.type, runAt: runAt ?? this.runAt, weekdays: weekdays ?? this.weekdays, ); } } class AutomationJobConfigModel { final String inputTemplate; final List enabledTools; final MessageContextConfigModel context; final ScheduleConfigModel schedule; AutomationJobConfigModel({ required this.inputTemplate, required this.enabledTools, required this.context, required this.schedule, }); factory AutomationJobConfigModel.fromJson(Map? json) { if (json == null) { return AutomationJobConfigModel( inputTemplate: '', enabledTools: const [], context: MessageContextConfigModel.fromJson(null), schedule: ScheduleConfigModel.fromJson(null), ); } return AutomationJobConfigModel( inputTemplate: _parseString( json['input_template'], field: 'input_template', fallback: '', ), enabledTools: _parseStringList( json['enabled_tools'], field: 'enabled_tools', ), context: json['context'] != null ? MessageContextConfigModel.fromJson( json['context'] as Map?, ) : MessageContextConfigModel.fromJson(null), schedule: json['schedule'] != null ? ScheduleConfigModel.fromJson( json['schedule'] as Map?, ) : ScheduleConfigModel.fromJson(null), ); } Map toJson() => { 'input_template': inputTemplate, 'enabled_tools': enabledTools, 'context': context.toJson(), 'schedule': schedule.toJson(), }; AutomationJobConfigModel copyWith({ String? inputTemplate, List? enabledTools, MessageContextConfigModel? context, ScheduleConfigModel? schedule, }) { return AutomationJobConfigModel( inputTemplate: inputTemplate ?? this.inputTemplate, enabledTools: enabledTools ?? this.enabledTools, context: context ?? this.context, schedule: schedule ?? this.schedule, ); } } class AutomationJobModel { final String id; final String ownerId; final String? bootstrapKey; final String title; final String timezone; final String status; final bool isSystem; final AutomationJobConfigModel config; final DateTime nextRunAt; final DateTime? lastRunAt; final DateTime createdAt; final DateTime updatedAt; AutomationJobModel({ required this.id, required this.ownerId, this.bootstrapKey, required this.title, required this.timezone, required this.status, required this.isSystem, required this.config, required this.nextRunAt, this.lastRunAt, required this.createdAt, required this.updatedAt, }); factory AutomationJobModel.fromJson(Map json) { return AutomationJobModel( id: _parseString(json['id'], field: 'id', fallback: ''), ownerId: _parseString(json['owner_id'], field: 'owner_id', fallback: ''), bootstrapKey: json['bootstrap_key'] == null ? null : _parseString( json['bootstrap_key'], field: 'bootstrap_key', fallback: '', ), title: _parseString(json['title'], field: 'title', fallback: ''), timezone: _parseString( json['timezone'], field: 'timezone', fallback: 'UTC', ), status: _parseString(json['status'], field: 'status', fallback: 'active'), isSystem: json['is_system'] == null ? false : (json['is_system'] is bool ? json['is_system'] as bool : throw FormatException('is_system invalid type')), config: json['config'] != null ? AutomationJobConfigModel.fromJson( json['config'] as Map?, ) : AutomationJobConfigModel.fromJson(null), nextRunAt: json['next_run_at'] != null ? DateTime.parse( _parseString( json['next_run_at'], field: 'next_run_at', fallback: '', ), ) : throw FormatException('next_run_at is required'), lastRunAt: json['last_run_at'] != null ? DateTime.parse( _parseString( json['last_run_at'], field: 'last_run_at', fallback: '', ), ) : null, createdAt: json['created_at'] != null ? DateTime.parse( _parseString( json['created_at'], field: 'created_at', fallback: '', ), ) : throw FormatException('created_at is required'), updatedAt: json['updated_at'] != null ? DateTime.parse( _parseString( json['updated_at'], field: 'updated_at', fallback: '', ), ) : throw FormatException('updated_at is required'), ); } Map toJson() => { 'id': id, 'owner_id': ownerId, 'bootstrap_key': bootstrapKey, 'title': title, 'timezone': timezone, 'status': status, 'is_system': isSystem, 'config': config.toJson(), 'next_run_at': nextRunAt.toIso8601String(), 'last_run_at': lastRunAt?.toIso8601String(), 'created_at': createdAt.toIso8601String(), 'updated_at': updatedAt.toIso8601String(), }; AutomationJobModel copyWith({ String? id, String? ownerId, String? bootstrapKey, String? title, String? timezone, String? status, bool? isSystem, AutomationJobConfigModel? config, DateTime? nextRunAt, DateTime? lastRunAt, DateTime? createdAt, DateTime? updatedAt, }) { return AutomationJobModel( id: id ?? this.id, ownerId: ownerId ?? this.ownerId, bootstrapKey: bootstrapKey ?? this.bootstrapKey, title: title ?? this.title, timezone: timezone ?? this.timezone, status: status ?? this.status, isSystem: isSystem ?? this.isSystem, config: config ?? this.config, nextRunAt: nextRunAt ?? this.nextRunAt, lastRunAt: lastRunAt ?? this.lastRunAt, createdAt: createdAt ?? this.createdAt, updatedAt: updatedAt ?? this.updatedAt, ); } bool get isActive => status.toLowerCase() == 'active'; bool get isDaily => config.schedule.type.toLowerCase() == 'daily'; bool get isWeekly => config.schedule.type.toLowerCase() == 'weekly'; } class AutomationJobListResponse { final List items; AutomationJobListResponse({required this.items}); factory AutomationJobListResponse.fromJson(Map? json) { if (json == null) { return AutomationJobListResponse(items: const []); } final itemsJson = json['items']; if (itemsJson == null) { return AutomationJobListResponse(items: const []); } if (itemsJson is List) { return AutomationJobListResponse( items: itemsJson .map((e) => AutomationJobModel.fromJson(e as Map)) .toList(), ); } throw FormatException('items invalid type'); } } class AutomationJobCreateRequest { final String title; final String timezone; final String status; final AutomationJobConfigModel config; AutomationJobCreateRequest({ required this.title, required this.timezone, required this.status, required this.config, }); Map toJson() => { 'title': title, 'timezone': timezone, 'status': status, 'config': config.toJson(), }; } class AutomationJobConfigPatchModel { final String? inputTemplate; final List? enabledTools; final MessageContextConfigModel? context; final ScheduleConfigModel? schedule; AutomationJobConfigPatchModel({ this.inputTemplate, this.enabledTools, this.context, this.schedule, }); Map toJson() { final map = {}; if (inputTemplate != null) map['input_template'] = inputTemplate; if (enabledTools != null) map['enabled_tools'] = enabledTools; if (context != null) map['context'] = context!.toJson(); if (schedule != null) map['schedule'] = schedule!.toJson(); return map; } } class AutomationJobUpdateRequest { final String? title; final String? timezone; final String? status; final AutomationJobConfigPatchModel? config; AutomationJobUpdateRequest({ this.title, this.timezone, this.status, this.config, }); Map toJson() { final map = {}; if (title != null) map['title'] = title; if (timezone != null) map['timezone'] = timezone; if (status != null) map['status'] = status; if (config != null) map['config'] = config!.toJson(); return map; } }