xworkmate-app/lib/runtime/runtime_models_runtime_payloads.part.dart

650 lines
19 KiB
Dart

part of 'runtime_models.dart';
class GatewayConnectionSnapshot {
const GatewayConnectionSnapshot({
required this.status,
required this.mode,
required this.statusText,
required this.serverName,
required this.remoteAddress,
required this.mainSessionKey,
required this.lastError,
required this.lastErrorCode,
required this.lastErrorDetailCode,
required this.lastConnectedAtMs,
required this.deviceId,
required this.authRole,
required this.authScopes,
required this.connectAuthMode,
required this.connectAuthFields,
required this.connectAuthSources,
required this.hasSharedAuth,
required this.hasDeviceToken,
required this.healthPayload,
required this.statusPayload,
});
final RuntimeConnectionStatus status;
final RuntimeConnectionMode mode;
final String statusText;
final String? serverName;
final String? remoteAddress;
final String? mainSessionKey;
final String? lastError;
final String? lastErrorCode;
final String? lastErrorDetailCode;
final int? lastConnectedAtMs;
final String? deviceId;
final String? authRole;
final List<String> authScopes;
final String? connectAuthMode;
final List<String> connectAuthFields;
final List<String> connectAuthSources;
final bool hasSharedAuth;
final bool hasDeviceToken;
final Map<String, dynamic>? healthPayload;
final Map<String, dynamic>? statusPayload;
factory GatewayConnectionSnapshot.initial({
RuntimeConnectionMode mode = RuntimeConnectionMode.unconfigured,
}) {
return GatewayConnectionSnapshot(
status: RuntimeConnectionStatus.offline,
mode: mode,
statusText: 'Offline',
serverName: null,
remoteAddress: null,
mainSessionKey: null,
lastError: null,
lastErrorCode: null,
lastErrorDetailCode: null,
lastConnectedAtMs: null,
deviceId: null,
authRole: null,
authScopes: const <String>[],
connectAuthMode: null,
connectAuthFields: const <String>[],
connectAuthSources: const <String>[],
hasSharedAuth: false,
hasDeviceToken: false,
healthPayload: null,
statusPayload: null,
);
}
GatewayConnectionSnapshot copyWith({
RuntimeConnectionStatus? status,
RuntimeConnectionMode? mode,
String? statusText,
String? serverName,
String? remoteAddress,
String? mainSessionKey,
String? lastError,
String? lastErrorCode,
String? lastErrorDetailCode,
int? lastConnectedAtMs,
String? deviceId,
String? authRole,
List<String>? authScopes,
String? connectAuthMode,
List<String>? connectAuthFields,
List<String>? connectAuthSources,
bool? hasSharedAuth,
bool? hasDeviceToken,
Map<String, dynamic>? healthPayload,
Map<String, dynamic>? statusPayload,
bool clearServerName = false,
bool clearRemoteAddress = false,
bool clearMainSessionKey = false,
bool clearLastError = false,
bool clearLastErrorCode = false,
bool clearLastErrorDetailCode = false,
}) {
return GatewayConnectionSnapshot(
status: status ?? this.status,
mode: mode ?? this.mode,
statusText: statusText ?? this.statusText,
serverName: clearServerName ? null : (serverName ?? this.serverName),
remoteAddress: clearRemoteAddress
? null
: (remoteAddress ?? this.remoteAddress),
mainSessionKey: clearMainSessionKey
? null
: (mainSessionKey ?? this.mainSessionKey),
lastError: clearLastError ? null : (lastError ?? this.lastError),
lastErrorCode: clearLastErrorCode
? null
: (lastErrorCode ?? this.lastErrorCode),
lastErrorDetailCode: clearLastErrorDetailCode
? null
: (lastErrorDetailCode ?? this.lastErrorDetailCode),
lastConnectedAtMs: lastConnectedAtMs ?? this.lastConnectedAtMs,
deviceId: deviceId ?? this.deviceId,
authRole: authRole ?? this.authRole,
authScopes: authScopes ?? this.authScopes,
connectAuthMode: connectAuthMode ?? this.connectAuthMode,
connectAuthFields: connectAuthFields ?? this.connectAuthFields,
connectAuthSources: connectAuthSources ?? this.connectAuthSources,
hasSharedAuth: hasSharedAuth ?? this.hasSharedAuth,
hasDeviceToken: hasDeviceToken ?? this.hasDeviceToken,
healthPayload: healthPayload ?? this.healthPayload,
statusPayload: statusPayload ?? this.statusPayload,
);
}
bool get pairingRequired {
final detailCode = lastErrorDetailCode?.trim().toUpperCase();
final errorCode = lastErrorCode?.trim().toUpperCase();
final errorText = lastError?.toLowerCase() ?? '';
return status != RuntimeConnectionStatus.connected &&
(detailCode == 'PAIRING_REQUIRED' ||
errorCode == 'NOT_PAIRED' ||
errorText.contains('pairing required'));
}
bool get gatewayTokenMissing {
final detailCode = lastErrorDetailCode?.trim().toUpperCase();
final errorText = lastError?.toLowerCase() ?? '';
return detailCode == 'AUTH_TOKEN_MISSING' ||
errorText.contains('gateway token missing');
}
String get connectAuthSummary {
final mode = connectAuthMode?.trim() ?? 'none';
final fields = connectAuthFields.isEmpty
? 'none'
: connectAuthFields.join(', ');
final sources = connectAuthSources.isEmpty
? 'none'
: connectAuthSources.join(' · ');
return '$mode | fields: $fields | sources: $sources';
}
}
class RuntimePackageInfo {
const RuntimePackageInfo({
required this.appName,
required this.packageName,
required this.version,
required this.buildNumber,
});
final String appName;
final String packageName;
final String version;
final String buildNumber;
}
class RuntimeDeviceInfo {
const RuntimeDeviceInfo({
required this.platform,
required this.platformVersion,
required this.deviceFamily,
required this.modelIdentifier,
});
final String platform;
final String platformVersion;
final String deviceFamily;
final String modelIdentifier;
String get platformLabel {
final version = platformVersion.trim();
if (version.isEmpty) {
return platform;
}
return '$platform $version';
}
}
class RuntimeLogEntry {
const RuntimeLogEntry({
required this.timestampMs,
required this.level,
required this.category,
required this.message,
});
final int timestampMs;
final String level;
final String category;
final String message;
String get timeLabel {
final date = DateTime.fromMillisecondsSinceEpoch(timestampMs);
return '${date.hour.toString().padLeft(2, '0')}:${date.minute.toString().padLeft(2, '0')}:${date.second.toString().padLeft(2, '0')}';
}
String get line => '[$timeLabel] ${level.toUpperCase()} $category $message';
}
class GatewayAgentSummary {
const GatewayAgentSummary({
required this.id,
required this.name,
required this.emoji,
required this.theme,
});
final String id;
final String name;
final String emoji;
final String theme;
}
class GatewaySessionSummary {
const GatewaySessionSummary({
required this.key,
required this.kind,
required this.displayName,
required this.surface,
required this.subject,
required this.room,
required this.space,
required this.updatedAtMs,
required this.sessionId,
required this.systemSent,
required this.abortedLastRun,
required this.thinkingLevel,
required this.verboseLevel,
required this.inputTokens,
required this.outputTokens,
required this.totalTokens,
required this.model,
required this.contextTokens,
required this.derivedTitle,
required this.lastMessagePreview,
});
final String key;
final String? kind;
final String? displayName;
final String? surface;
final String? subject;
final String? room;
final String? space;
final double? updatedAtMs;
final String? sessionId;
final bool? systemSent;
final bool? abortedLastRun;
final String? thinkingLevel;
final String? verboseLevel;
final int? inputTokens;
final int? outputTokens;
final int? totalTokens;
final String? model;
final int? contextTokens;
final String? derivedTitle;
final String? lastMessagePreview;
String get label {
final candidates = [derivedTitle, displayName, subject, room, space, key];
return candidates.firstWhere(
(item) => item != null && item.trim().isNotEmpty,
orElse: () => key,
)!;
}
}
class GatewayChatMessage {
const GatewayChatMessage({
required this.id,
required this.role,
required this.text,
required this.timestampMs,
required this.toolCallId,
required this.toolName,
required this.stopReason,
required this.pending,
required this.error,
});
final String id;
final String role;
final String text;
final double? timestampMs;
final String? toolCallId;
final String? toolName;
final String? stopReason;
final bool pending;
final bool error;
Map<String, dynamic> toJson() {
return {
'id': id,
'role': role,
'text': text,
'timestampMs': timestampMs,
'toolCallId': toolCallId,
'toolName': toolName,
'stopReason': stopReason,
'pending': pending,
'error': error,
};
}
factory GatewayChatMessage.fromJson(Map<String, dynamic> json) {
double? asDouble(Object? value) {
if (value is num) {
return value.toDouble();
}
return double.tryParse(value?.toString() ?? '');
}
return GatewayChatMessage(
id: json['id']?.toString() ?? '',
role: json['role']?.toString() ?? 'assistant',
text: json['text']?.toString() ?? '',
timestampMs: asDouble(json['timestampMs']),
toolCallId: json['toolCallId']?.toString(),
toolName: json['toolName']?.toString(),
stopReason: json['stopReason']?.toString(),
pending: json['pending'] as bool? ?? false,
error: json['error'] as bool? ?? false,
);
}
GatewayChatMessage copyWith({
String? id,
String? role,
String? text,
double? timestampMs,
String? toolCallId,
String? toolName,
String? stopReason,
bool? pending,
bool? error,
}) {
return GatewayChatMessage(
id: id ?? this.id,
role: role ?? this.role,
text: text ?? this.text,
timestampMs: timestampMs ?? this.timestampMs,
toolCallId: toolCallId ?? this.toolCallId,
toolName: toolName ?? this.toolName,
stopReason: stopReason ?? this.stopReason,
pending: pending ?? this.pending,
error: error ?? this.error,
);
}
}
class AssistantThreadSkillEntry {
const AssistantThreadSkillEntry({
required this.key,
required this.label,
required this.description,
this.source = '',
required this.sourcePath,
this.scope = '',
required this.sourceLabel,
});
final String key;
final String label;
final String description;
final String source;
final String sourcePath;
final String scope;
final String sourceLabel;
AssistantThreadSkillEntry copyWith({
String? key,
String? label,
String? description,
String? source,
String? sourcePath,
String? scope,
String? sourceLabel,
}) {
return AssistantThreadSkillEntry(
key: key ?? this.key,
label: label ?? this.label,
description: description ?? this.description,
source: source ?? this.source,
sourcePath: sourcePath ?? this.sourcePath,
scope: scope ?? this.scope,
sourceLabel: sourceLabel ?? this.sourceLabel,
);
}
Map<String, dynamic> toJson() {
return {
'key': key,
'label': label,
'description': description,
'source': source,
'sourcePath': sourcePath,
'scope': scope,
'sourceLabel': sourceLabel,
};
}
factory AssistantThreadSkillEntry.fromJson(Map<String, dynamic> json) {
return AssistantThreadSkillEntry(
key: json['key']?.toString() ?? '',
label: json['label']?.toString() ?? '',
description: json['description']?.toString() ?? '',
source: json['source']?.toString() ?? '',
sourcePath: json['sourcePath']?.toString() ?? '',
scope: json['scope']?.toString() ?? '',
sourceLabel: json['sourceLabel']?.toString() ?? '',
);
}
}
class AssistantThreadRecord {
const AssistantThreadRecord({
required this.sessionKey,
required this.messages,
required this.updatedAtMs,
required this.title,
required this.archived,
required this.executionTarget,
required this.messageViewMode,
this.importedSkills = const <AssistantThreadSkillEntry>[],
this.selectedSkillKeys = const <String>[],
this.assistantModelId = '',
this.singleAgentProvider = SingleAgentProvider.auto,
this.gatewayEntryState,
this.workspaceRef = '',
this.workspaceRefKind = WorkspaceRefKind.localPath,
});
final String sessionKey;
final List<GatewayChatMessage> messages;
final double? updatedAtMs;
final String title;
final bool archived;
final AssistantExecutionTarget? executionTarget;
final AssistantMessageViewMode messageViewMode;
final List<AssistantThreadSkillEntry> importedSkills;
final List<String> selectedSkillKeys;
final String assistantModelId;
final SingleAgentProvider singleAgentProvider;
final String? gatewayEntryState;
final String workspaceRef;
final WorkspaceRefKind workspaceRefKind;
AssistantThreadRecord copyWith({
String? sessionKey,
List<GatewayChatMessage>? messages,
double? updatedAtMs,
String? title,
bool? archived,
AssistantExecutionTarget? executionTarget,
bool clearExecutionTarget = false,
AssistantMessageViewMode? messageViewMode,
List<AssistantThreadSkillEntry>? importedSkills,
List<String>? selectedSkillKeys,
String? assistantModelId,
SingleAgentProvider? singleAgentProvider,
String? gatewayEntryState,
bool clearGatewayEntryState = false,
String? workspaceRef,
WorkspaceRefKind? workspaceRefKind,
}) {
return AssistantThreadRecord(
sessionKey: sessionKey ?? this.sessionKey,
messages: messages ?? this.messages,
updatedAtMs: updatedAtMs ?? this.updatedAtMs,
title: title ?? this.title,
archived: archived ?? this.archived,
executionTarget: clearExecutionTarget
? null
: (executionTarget ?? this.executionTarget),
messageViewMode: messageViewMode ?? this.messageViewMode,
importedSkills: importedSkills ?? this.importedSkills,
selectedSkillKeys: selectedSkillKeys ?? this.selectedSkillKeys,
assistantModelId: assistantModelId ?? this.assistantModelId,
singleAgentProvider: singleAgentProvider ?? this.singleAgentProvider,
gatewayEntryState: clearGatewayEntryState
? null
: (gatewayEntryState ?? this.gatewayEntryState),
workspaceRef: workspaceRef ?? this.workspaceRef,
workspaceRefKind: workspaceRefKind ?? this.workspaceRefKind,
);
}
Map<String, dynamic> toJson() {
return {
'sessionKey': sessionKey,
'messages': messages.map((item) => item.toJson()).toList(growable: false),
'updatedAtMs': updatedAtMs,
'title': title,
'archived': archived,
'executionTarget': executionTarget?.name,
'messageViewMode': messageViewMode.name,
'importedSkills': importedSkills
.map((item) => item.toJson())
.toList(growable: false),
'selectedSkillKeys': selectedSkillKeys,
'assistantModelId': assistantModelId,
'singleAgentProvider': singleAgentProvider.providerId,
'gatewayEntryState': gatewayEntryState,
'workspaceRef': workspaceRef,
'workspaceRefKind': workspaceRefKind.name,
};
}
factory AssistantThreadRecord.fromJson(Map<String, dynamic> json) {
double? asDouble(Object? value) {
if (value is num) {
return value.toDouble();
}
return double.tryParse(value?.toString() ?? '');
}
final rawMessages = json['messages'];
final messages = rawMessages is List
? rawMessages
.whereType<Map>()
.map(
(item) =>
GatewayChatMessage.fromJson(item.cast<String, dynamic>()),
)
.toList(growable: false)
: const <GatewayChatMessage>[];
List<AssistantThreadSkillEntry> normalizeSkillEntries(Object? value) {
if (value is! List) {
return const <AssistantThreadSkillEntry>[];
}
final entries = <AssistantThreadSkillEntry>[];
final seen = <String>{};
for (final item in value.whereType<Map>()) {
final entry = AssistantThreadSkillEntry.fromJson(
item.cast<String, dynamic>(),
);
final normalizedKey = entry.key.trim();
if (normalizedKey.isEmpty || !seen.add(normalizedKey)) {
continue;
}
entries.add(entry);
}
return entries;
}
List<String> normalizeSkillKeys(Object? value) {
if (value is! List) {
return const <String>[];
}
final keys = <String>[];
final seen = <String>{};
for (final item in value) {
final normalized = item?.toString().trim() ?? '';
if (normalized.isEmpty || !seen.add(normalized)) {
continue;
}
keys.add(normalized);
}
return keys;
}
String? normalizeGatewayEntryState(Object? value) {
final normalized = value?.toString().trim() ?? '';
if (normalized.isEmpty) {
return null;
}
if (normalized == 'ai-gateway-only') {
return 'single-agent';
}
return normalized;
}
WorkspaceRefKind normalizeWorkspaceRefKind(
Object? value, {
required AssistantExecutionTarget? executionTarget,
required String workspaceRef,
}) {
final raw = value?.toString().trim();
if (raw != null && raw.isNotEmpty) {
return WorkspaceRefKindCopy.fromJsonValue(raw);
}
if (workspaceRef.startsWith('object://')) {
return WorkspaceRefKind.objectStore;
}
if (executionTarget != null &&
executionTarget != AssistantExecutionTarget.singleAgent) {
return WorkspaceRefKind.remotePath;
}
return WorkspaceRefKind.localPath;
}
// Keep tolerating legacy payloads that still contain discoveredSkills,
// but do not map the retired field back into the runtime model.
normalizeSkillEntries(json['discoveredSkills']);
final executionTarget = json['executionTarget'] == null
? null
: AssistantExecutionTargetCopy.fromJsonValue(
json['executionTarget']?.toString(),
);
final workspaceRef = json['workspaceRef']?.toString() ?? '';
return AssistantThreadRecord(
sessionKey: json['sessionKey']?.toString() ?? '',
messages: messages,
updatedAtMs: asDouble(json['updatedAtMs']),
title: json['title']?.toString() ?? '',
archived: json['archived'] as bool? ?? false,
executionTarget: executionTarget,
messageViewMode: AssistantMessageViewModeCopy.fromJsonValue(
json['messageViewMode']?.toString(),
),
importedSkills: normalizeSkillEntries(json['importedSkills']),
selectedSkillKeys: normalizeSkillKeys(json['selectedSkillKeys']),
assistantModelId: json['assistantModelId']?.toString() ?? '',
singleAgentProvider: SingleAgentProviderCopy.fromJsonValue(
json['singleAgentProvider']?.toString(),
),
gatewayEntryState: normalizeGatewayEntryState(json['gatewayEntryState']),
workspaceRef: workspaceRef,
workspaceRefKind: normalizeWorkspaceRefKind(
json['workspaceRefKind'],
executionTarget: executionTarget,
workspaceRef: workspaceRef,
),
);
}
}