HostExtension constructor
HostExtension({})
Validates and builds. Throws HostProfileViolation on an E6 gap (missing built-in profile, blank reason, a transport state — tool extensions have no transport dimension), an empty name, or an empty/duplicate tool id.
Implementation
HostExtension({
required this.name,
List<AgentTool> tools = const [],
required Map<String, CapabilityState> profileStates,
}) : tools = List.unmodifiable(tools),
profileStates = Map.unmodifiable(profileStates) {
if (name.trim().isEmpty) {
throw const HostProfileViolation(
'HostExtension name must not be blank (E7 names registrants — '
'they need a real id).',
);
}
final missing = builtInProfiles.keys
.where((profile) => !profileStates.containsKey(profile))
.toList();
if (missing.isNotEmpty) {
throw HostProfileViolation(
'HostExtension "$name" declares no state for built-in profile(s) '
'${missing.map((m) => '"$m"').join(', ')} — declare every built-in '
'profile explicitly (E6: there is no implicit default).',
);
}
for (final entry in profileStates.entries) {
if (entry.value is CapabilityTransportState) {
throw HostProfileViolation(
'HostExtension "$name" declares a transport state for profile '
'"${entry.key}" — tool extensions have no transport dimension; '
'declare on or off(reason).',
);
}
if (entry.value.reason case final reason? when reason.trim().isEmpty) {
throw HostProfileViolation(
'HostExtension "$name" is off on "${entry.key}" without a reason '
'— off is never silent (E8).',
);
}
}
final seen = <String>{};
for (final tool in tools) {
if (tool.name.trim().isEmpty) {
throw HostProfileViolation(
'HostExtension "$name" contributes a tool with a blank id.',
);
}
if (!seen.add(tool.name)) {
throw HostProfileViolation(
'HostExtension "$name" contributes "${tool.name}" twice — the '
'builder rejects duplicate ids at build time (E7).',
);
}
}
}