394 lines
12 KiB
Dart
394 lines
12 KiB
Dart
class AgUiEventTypeWire {
|
|
static const runStarted = 'RUN_STARTED';
|
|
static const runFinished = 'RUN_FINISHED';
|
|
static const runError = 'RUN_ERROR';
|
|
static const stepStarted = 'STEP_STARTED';
|
|
static const stepFinished = 'STEP_FINISHED';
|
|
static const textMessageEnd = 'TEXT_MESSAGE_END';
|
|
static const toolCallStart = 'TOOL_CALL_START';
|
|
static const toolCallArgs = 'TOOL_CALL_ARGS';
|
|
static const toolCallEnd = 'TOOL_CALL_END';
|
|
static const toolCallResult = 'TOOL_CALL_RESULT';
|
|
static const toolCallError = 'TOOL_CALL_ERROR';
|
|
}
|
|
|
|
enum AgUiEventType {
|
|
runStarted,
|
|
runFinished,
|
|
runError,
|
|
stepStarted,
|
|
stepFinished,
|
|
textMessageEnd,
|
|
toolCallStart,
|
|
toolCallArgs,
|
|
toolCallEnd,
|
|
toolCallResult,
|
|
toolCallError,
|
|
unknown,
|
|
}
|
|
|
|
const _wireToTypeMap = {
|
|
AgUiEventTypeWire.runStarted: AgUiEventType.runStarted,
|
|
AgUiEventTypeWire.runFinished: AgUiEventType.runFinished,
|
|
AgUiEventTypeWire.runError: AgUiEventType.runError,
|
|
AgUiEventTypeWire.stepStarted: AgUiEventType.stepStarted,
|
|
AgUiEventTypeWire.stepFinished: AgUiEventType.stepFinished,
|
|
AgUiEventTypeWire.textMessageEnd: AgUiEventType.textMessageEnd,
|
|
AgUiEventTypeWire.toolCallStart: AgUiEventType.toolCallStart,
|
|
AgUiEventTypeWire.toolCallArgs: AgUiEventType.toolCallArgs,
|
|
AgUiEventTypeWire.toolCallEnd: AgUiEventType.toolCallEnd,
|
|
AgUiEventTypeWire.toolCallResult: AgUiEventType.toolCallResult,
|
|
AgUiEventTypeWire.toolCallError: AgUiEventType.toolCallError,
|
|
};
|
|
|
|
const _typeToWireMap = {
|
|
AgUiEventType.runStarted: AgUiEventTypeWire.runStarted,
|
|
AgUiEventType.runFinished: AgUiEventTypeWire.runFinished,
|
|
AgUiEventType.runError: AgUiEventTypeWire.runError,
|
|
AgUiEventType.stepStarted: AgUiEventTypeWire.stepStarted,
|
|
AgUiEventType.stepFinished: AgUiEventTypeWire.stepFinished,
|
|
AgUiEventType.textMessageEnd: AgUiEventTypeWire.textMessageEnd,
|
|
AgUiEventType.toolCallStart: AgUiEventTypeWire.toolCallStart,
|
|
AgUiEventType.toolCallArgs: AgUiEventTypeWire.toolCallArgs,
|
|
AgUiEventType.toolCallEnd: AgUiEventTypeWire.toolCallEnd,
|
|
AgUiEventType.toolCallResult: AgUiEventTypeWire.toolCallResult,
|
|
AgUiEventType.toolCallError: AgUiEventTypeWire.toolCallError,
|
|
AgUiEventType.unknown: '',
|
|
};
|
|
|
|
AgUiEventType agUiEventTypeFromWire(String wire) =>
|
|
_wireToTypeMap[wire] ?? AgUiEventType.unknown;
|
|
|
|
String agUiEventTypeToWire(AgUiEventType type) => _typeToWireMap[type] ?? '';
|
|
|
|
abstract class AgUiEvent {
|
|
const AgUiEvent({required this.type});
|
|
|
|
final AgUiEventType type;
|
|
|
|
factory AgUiEvent.fromJson(Map<String, dynamic> json) {
|
|
final wireType = json['type'];
|
|
final type = wireType is String
|
|
? agUiEventTypeFromWire(wireType)
|
|
: AgUiEventType.unknown;
|
|
return switch (type) {
|
|
AgUiEventType.runStarted => RunStartedEvent.fromJson(json),
|
|
AgUiEventType.runFinished => RunFinishedEvent.fromJson(json),
|
|
AgUiEventType.runError => RunErrorEvent.fromJson(json),
|
|
AgUiEventType.stepStarted => StepStartedEvent.fromJson(json),
|
|
AgUiEventType.stepFinished => StepFinishedEvent.fromJson(json),
|
|
AgUiEventType.textMessageEnd => TextMessageEndEvent.fromJson(json),
|
|
AgUiEventType.toolCallStart => ToolCallStartEvent.fromJson(json),
|
|
AgUiEventType.toolCallArgs => ToolCallArgsEvent.fromJson(json),
|
|
AgUiEventType.toolCallEnd => ToolCallEndEvent.fromJson(json),
|
|
AgUiEventType.toolCallResult => ToolCallResultEvent.fromJson(json),
|
|
AgUiEventType.toolCallError => ToolCallErrorEvent.fromJson(json),
|
|
AgUiEventType.unknown => UnknownAgUiEvent(rawJson: json),
|
|
};
|
|
}
|
|
}
|
|
|
|
class UnknownAgUiEvent extends AgUiEvent {
|
|
const UnknownAgUiEvent({required this.rawJson})
|
|
: super(type: AgUiEventType.unknown);
|
|
|
|
final Map<String, dynamic> rawJson;
|
|
}
|
|
|
|
class RunStartedEvent extends AgUiEvent {
|
|
RunStartedEvent({required this.threadId, required this.runId})
|
|
: super(type: AgUiEventType.runStarted);
|
|
|
|
final String threadId;
|
|
final String runId;
|
|
|
|
factory RunStartedEvent.fromJson(Map<String, dynamic> json) =>
|
|
RunStartedEvent(
|
|
threadId: _asString(json['threadId']),
|
|
runId: _asString(json['runId']),
|
|
);
|
|
}
|
|
|
|
class RunFinishedEvent extends AgUiEvent {
|
|
RunFinishedEvent({required this.threadId, required this.runId})
|
|
: super(type: AgUiEventType.runFinished);
|
|
|
|
final String threadId;
|
|
final String runId;
|
|
|
|
factory RunFinishedEvent.fromJson(Map<String, dynamic> json) =>
|
|
RunFinishedEvent(
|
|
threadId: _asString(json['threadId']),
|
|
runId: _asString(json['runId']),
|
|
);
|
|
}
|
|
|
|
class RunErrorEvent extends AgUiEvent {
|
|
RunErrorEvent({required this.message, this.code})
|
|
: super(type: AgUiEventType.runError);
|
|
|
|
final String message;
|
|
final String? code;
|
|
|
|
factory RunErrorEvent.fromJson(Map<String, dynamic> json) => RunErrorEvent(
|
|
message: _asString(json['message'], fallback: 'Unknown error'),
|
|
code: json['code'] as String?,
|
|
);
|
|
}
|
|
|
|
class StepStartedEvent extends AgUiEvent {
|
|
StepStartedEvent({required this.stepName})
|
|
: super(type: AgUiEventType.stepStarted);
|
|
|
|
final String stepName;
|
|
|
|
factory StepStartedEvent.fromJson(Map<String, dynamic> json) =>
|
|
StepStartedEvent(stepName: _asString(json['stepName']));
|
|
}
|
|
|
|
class StepFinishedEvent extends AgUiEvent {
|
|
StepFinishedEvent({required this.stepName})
|
|
: super(type: AgUiEventType.stepFinished);
|
|
|
|
final String stepName;
|
|
|
|
factory StepFinishedEvent.fromJson(Map<String, dynamic> json) =>
|
|
StepFinishedEvent(stepName: _asString(json['stepName']));
|
|
}
|
|
|
|
class TextMessageEndEvent extends AgUiEvent {
|
|
TextMessageEndEvent({
|
|
required this.messageId,
|
|
required this.answer,
|
|
required this.role,
|
|
required this.status,
|
|
required this.uiSchema,
|
|
}) : super(type: AgUiEventType.textMessageEnd);
|
|
|
|
final String messageId;
|
|
final String answer;
|
|
final String role;
|
|
final String status;
|
|
final Map<String, dynamic>? uiSchema;
|
|
|
|
factory TextMessageEndEvent.fromJson(Map<String, dynamic> json) =>
|
|
TextMessageEndEvent(
|
|
messageId: _asString(json['messageId']),
|
|
answer: _asString(json['answer']),
|
|
role: _asString(json['role'], fallback: 'assistant'),
|
|
status: _asString(json['status'], fallback: 'success'),
|
|
uiSchema: _asMap(json['ui_schema']),
|
|
);
|
|
}
|
|
|
|
class ToolCallStartEvent extends AgUiEvent {
|
|
ToolCallStartEvent({required this.toolCallId, required this.toolCallName})
|
|
: super(type: AgUiEventType.toolCallStart);
|
|
|
|
final String toolCallId;
|
|
final String toolCallName;
|
|
|
|
factory ToolCallStartEvent.fromJson(Map<String, dynamic> json) =>
|
|
ToolCallStartEvent(
|
|
toolCallId: _asString(json['toolCallId']),
|
|
toolCallName: _asString(json['toolCallName']),
|
|
);
|
|
}
|
|
|
|
class ToolCallArgsEvent extends AgUiEvent {
|
|
ToolCallArgsEvent({required this.toolCallId, required this.args})
|
|
: super(type: AgUiEventType.toolCallArgs);
|
|
|
|
final String toolCallId;
|
|
final Map<String, dynamic> args;
|
|
|
|
factory ToolCallArgsEvent.fromJson(Map<String, dynamic> json) =>
|
|
ToolCallArgsEvent(
|
|
toolCallId: _asString(json['toolCallId']),
|
|
args: _asMap(json['args']) ?? const {},
|
|
);
|
|
}
|
|
|
|
class ToolCallEndEvent extends AgUiEvent {
|
|
ToolCallEndEvent({required this.toolCallId})
|
|
: super(type: AgUiEventType.toolCallEnd);
|
|
|
|
final String toolCallId;
|
|
|
|
factory ToolCallEndEvent.fromJson(Map<String, dynamic> json) =>
|
|
ToolCallEndEvent(toolCallId: _asString(json['toolCallId']));
|
|
}
|
|
|
|
class ToolCallResultEvent extends AgUiEvent {
|
|
ToolCallResultEvent({
|
|
required this.messageId,
|
|
required this.toolCallId,
|
|
required this.toolName,
|
|
required this.resultSummary,
|
|
required this.status,
|
|
required this.uiSchema,
|
|
}) : super(type: AgUiEventType.toolCallResult);
|
|
|
|
final String messageId;
|
|
final String toolCallId;
|
|
final String toolName;
|
|
final String resultSummary;
|
|
final String status;
|
|
final Map<String, dynamic>? uiSchema;
|
|
|
|
factory ToolCallResultEvent.fromJson(Map<String, dynamic> json) =>
|
|
ToolCallResultEvent(
|
|
messageId: _asString(json['messageId']),
|
|
toolCallId: _asString(json['tool_call_id']),
|
|
toolName: _asString(json['tool_name']),
|
|
resultSummary: _asString(json['result']),
|
|
status: _asString(json['status'], fallback: 'success'),
|
|
uiSchema: _asMap(json['ui_schema']),
|
|
);
|
|
}
|
|
|
|
class ToolCallErrorEvent extends AgUiEvent {
|
|
ToolCallErrorEvent({required this.toolCallId, required this.error, this.code})
|
|
: super(type: AgUiEventType.toolCallError);
|
|
|
|
final String toolCallId;
|
|
final String error;
|
|
final String? code;
|
|
|
|
factory ToolCallErrorEvent.fromJson(Map<String, dynamic> json) =>
|
|
ToolCallErrorEvent(
|
|
toolCallId: _asString(json['toolCallId']),
|
|
error: _asString(json['error'], fallback: 'Tool call failed'),
|
|
code: json['code'] as String?,
|
|
);
|
|
}
|
|
|
|
class HistorySnapshot {
|
|
const HistorySnapshot({
|
|
required this.scope,
|
|
required this.threadId,
|
|
required this.day,
|
|
required this.hasMore,
|
|
required this.messages,
|
|
});
|
|
|
|
final String scope;
|
|
final String? threadId;
|
|
final String? day;
|
|
final bool hasMore;
|
|
final List<HistoryMessage> messages;
|
|
|
|
factory HistorySnapshot.fromJson(Map<String, dynamic> json) {
|
|
final rawMessages = json['messages'];
|
|
final messages = rawMessages is List
|
|
? rawMessages
|
|
.whereType<Map<String, dynamic>>()
|
|
.map(HistoryMessage.fromJson)
|
|
.toList()
|
|
: const <HistoryMessage>[];
|
|
return HistorySnapshot(
|
|
scope: _asString(json['scope'], fallback: 'history_day'),
|
|
threadId: json['threadId'] as String?,
|
|
day: json['day'] as String?,
|
|
hasMore: json['hasMore'] == true,
|
|
messages: messages,
|
|
);
|
|
}
|
|
}
|
|
|
|
class HistoryMessage {
|
|
const HistoryMessage({
|
|
required this.id,
|
|
required this.seq,
|
|
required this.role,
|
|
required this.content,
|
|
required this.timestamp,
|
|
this.attachments = const <HistoryAttachment>[],
|
|
this.uiSchema,
|
|
});
|
|
|
|
final String id;
|
|
final int seq;
|
|
final String role;
|
|
final String content;
|
|
final DateTime timestamp;
|
|
final List<HistoryAttachment> attachments;
|
|
final Map<String, dynamic>? uiSchema;
|
|
|
|
factory HistoryMessage.fromJson(Map<String, dynamic> json) => HistoryMessage(
|
|
id: _asString(json['id']),
|
|
seq: _asInt(json['seq']),
|
|
role: _asString(json['role']),
|
|
content: _asString(json['content']),
|
|
timestamp:
|
|
DateTime.tryParse(_asString(json['timestamp'])) ?? DateTime.now(),
|
|
attachments: _parseHistoryAttachments(json['attachments']),
|
|
uiSchema: _asMap(json['ui_schema']),
|
|
);
|
|
}
|
|
|
|
class HistoryAttachment {
|
|
const HistoryAttachment({required this.url, required this.mimeType});
|
|
|
|
final String url;
|
|
final String mimeType;
|
|
|
|
factory HistoryAttachment.fromJson(Map<String, dynamic> json) {
|
|
return HistoryAttachment(
|
|
url: _asString(json['url']),
|
|
mimeType: _asString(json['mimeType']),
|
|
);
|
|
}
|
|
}
|
|
|
|
String _asString(Object? value, {String fallback = ''}) {
|
|
if (value is String) {
|
|
return value;
|
|
}
|
|
return fallback;
|
|
}
|
|
|
|
int _asInt(Object? value) {
|
|
if (value is int) {
|
|
return value;
|
|
}
|
|
if (value is double) {
|
|
return value.toInt();
|
|
}
|
|
if (value is String) {
|
|
return int.tryParse(value) ?? 0;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
Map<String, dynamic>? _asMap(Object? value) {
|
|
if (value is Map<String, dynamic>) {
|
|
return value;
|
|
}
|
|
if (value is Map) {
|
|
final result = <String, dynamic>{};
|
|
for (final entry in value.entries) {
|
|
final key = entry.key;
|
|
if (key is String) {
|
|
result[key] = entry.value;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
List<HistoryAttachment> _parseHistoryAttachments(Object? value) {
|
|
if (value is! List) {
|
|
return const <HistoryAttachment>[];
|
|
}
|
|
return value
|
|
.whereType<Map<String, dynamic>>()
|
|
.map(HistoryAttachment.fromJson)
|
|
.where(
|
|
(attachment) =>
|
|
attachment.url.isNotEmpty && attachment.mimeType.isNotEmpty,
|
|
)
|
|
.toList();
|
|
}
|