write static method

void write({
  1. AiProviderKind? provider,
  2. String? model,
  3. String? apiKey,
  4. String? plannerModel,
  5. String? executorModel,
  6. String? explainerModel,
  7. AgentMode? mode,
  8. String? language,
  9. String? baseUrl,
  10. int? maxSteps,
  11. String? path,
  12. String? home,
})

Writes the given fields into ai.yaml, updating only the keys provided and preserving the rest (and surrounding comments). Creates the file and its parent directory (mode 600/700 on POSIX) when missing.

A null value leaves the key untouched. An empty-string value clears the key — it is removed from the file so resolution falls back to the default (e.g. clearing model reverts to the per-provider default; clearing executorModel reverts to model).

Implementation

static void write({
  AiProviderKind? provider,
  String? model,
  String? apiKey,
  String? plannerModel,
  String? executorModel,
  String? explainerModel,
  AgentMode? mode,
  String? language,
  String? baseUrl,
  int? maxSteps,
  String? path,
  String? home,
}) {
  // Partition the requested changes: non-empty values are written, while
  // empty strings mean "clear" — the key is removed so resolution uses the
  // default. `null` values are skipped entirely (untouched).
  final fields = <String, Object?>{
    'provider': provider?.wireName,
    'model': model,
    'apiKey': apiKey,
    'plannerModel': plannerModel,
    'executorModel': executorModel,
    'explainerModel': explainerModel,
    'mode': mode?.wireName,
    'language': language,
    'baseUrl': baseUrl,
    'maxSteps': maxSteps,
  };
  final updates = <String, Object>{};
  final removals = <String>[];
  for (final e in fields.entries) {
    final v = e.value;
    if (v == null) continue;
    if (v is String && v.isEmpty) {
      removals.add(e.key);
    } else {
      updates[e.key] = v;
    }
  }
  if (updates.isEmpty && removals.isEmpty) return;

  final file = File(path ?? defaultPath(home: home));
  final existing = file.existsSync() ? file.readAsStringSync() : '';

  if (existing.trim().isEmpty) {
    // Nothing to remove from a fresh file; only the set values are written.
    if (updates.isEmpty) return;
    file.parent.createSync(recursive: true);
    _restrictDir(file.parent);
    file.writeAsStringSync(_template(updates));
    _restrictFile(file);
    return;
  }

  final doc = loadYaml(existing);
  if (doc is Map && doc['ai'] is Map) {
    final aiMap = doc['ai'] as Map;
    final editor = YamlEditor(existing);
    for (final e in updates.entries) {
      editor.update(['ai', e.key], e.value);
    }
    for (final key in removals) {
      if (aiMap.containsKey(key)) editor.remove(['ai', key]);
    }
    file.writeAsStringSync(editor.toString());
  } else if (doc is Map) {
    if (updates.isEmpty) return; // no `ai` map yet, nothing to remove
    final editor = YamlEditor(existing)..update(['ai'], updates);
    file.writeAsStringSync(editor.toString());
  } else {
    // Root is not a mapping (unexpected); replace with a fresh template.
    if (updates.isEmpty) return;
    file.writeAsStringSync(_template(updates));
  }
  _restrictFile(file);
}