StuckToolConfig.fromYaml constructor

StuckToolConfig.fromYaml(
  1. Object? node
)

Implementation

factory StuckToolConfig.fromYaml(Object? node) {
  if (node == null) return const StuckToolConfig();
  if (node is! Map) {
    throw ConfigException('agent.stuckTool must be a map, got: $node');
  }
  int seconds(String key, int fallback, {bool positive = false}) {
    final value = node[key];
    if (value == null) return fallback;
    if (value is! int) {
      throw ConfigException('"agent.stuckTool.$key" must be an integer');
    }
    // A heartbeat cadence of zero would spin the event loop
    // (Timer.periodic(Duration.zero)); a grace/floor of zero is a legal
    // "immediately" knob (gh-1054 review).
    if (positive ? value <= 0 : value < 0) {
      throw ConfigException(
        '"agent.stuckTool.$key" must be '
        '${positive ? '> 0' : '>= 0'}',
      );
    }
    return value;
  }

  var enabled = true;
  var followUp = StuckFollowUpMode.autonomous;
  var floorSeconds = defaultStuckFloorSeconds;
  num factor = defaultStuckDeclaredTimeoutFactor;
  var heartbeatSeconds = defaultStuckHeartbeatSeconds;
  var cancelGraceSeconds = defaultStuckCancelGraceSeconds;
  var excludeTools = defaultStuckExcludedTools;
  for (final key in node.keys) {
    switch (key) {
      case 'enabled':
        final value = node[key];
        if (value is! bool) {
          throw ConfigException(
            '"agent.stuckTool.enabled" must be a boolean',
          );
        }
        enabled = value;
      case 'followUp':
        final value = node[key];
        if (value is! String) {
          throw ConfigException(
            '"agent.stuckTool.followUp" must be a string '
            '(autonomous | advisory)',
          );
        }
        followUp = switch (value) {
          'autonomous' => StuckFollowUpMode.autonomous,
          'advisory' => StuckFollowUpMode.advisory,
          _ => throw ConfigException(
            '"agent.stuckTool.followUp" must be "autonomous" or '
            '"advisory", got: $value',
          ),
        };
      case 'floorSeconds':
        floorSeconds = seconds('floorSeconds', defaultStuckFloorSeconds);
      case 'declaredTimeoutFactor':
        final value = node[key];
        if (value is! num || value <= 0) {
          throw ConfigException(
            '"agent.stuckTool.declaredTimeoutFactor" must be a positive '
            'number',
          );
        }
        factor = value;
      case 'heartbeatSeconds':
        heartbeatSeconds = seconds(
          'heartbeatSeconds',
          defaultStuckHeartbeatSeconds,
          positive: true,
        );
      case 'cancelGraceSeconds':
        cancelGraceSeconds = seconds(
          'cancelGraceSeconds',
          defaultStuckCancelGraceSeconds,
        );
      case 'excludeTools':
        final value = node[key];
        if (value is! List || value.any((entry) => entry is! String)) {
          throw ConfigException(
            '"agent.stuckTool.excludeTools" must be a list of tool names',
          );
        }
        excludeTools = List<String>.from(value);
      default:
        throw ConfigException('unknown "agent.stuckTool" key: $key');
    }
  }
  return StuckToolConfig(
    enabled: enabled,
    followUp: followUp,
    floor: Duration(seconds: floorSeconds),
    declaredTimeoutFactor: factor,
    heartbeatInterval: Duration(seconds: heartbeatSeconds),
    cancelGrace: Duration(seconds: cancelGraceSeconds),
    excludeTools: excludeTools,
  );
}