Files
ClawdDotNet/src/ClawdDotNet.Core/Config/ConfigLoader.cs
T
RichardandClaude Opus 5.5 837a634a22 Stufe 4c (#764): Kommentare in C#, WebUI-Skripten, CSS und HTML auf Englisch
Rund 2.600 Kommentarblöcke in 310 C#-Dateien übersetzt, Begründungen vollständig
erhalten: blockweise gleiche Anzahl wie vorher, kein Block unter 60 % seiner
Länge, der Code außerhalb der Kommentare per Tokenizer byte-identisch geprüft.
Zitate deutscher Oberflächentexte und Testdaten bleiben deutsch.

WebUI: Kommentare in JS/CSS/HTML übersetzt, JSDoc-@param an die in 4b
umbenannten Parameter angeglichen.

Dabei behoben:
- 4b hatte "tag" (HTML-Tag) als deutsches "Tag" gelesen und zu "dayValue"
  gemacht (makeElement, svg, Tagesübersicht) - zurück auf tag/day.
- Aufgaben "InProgress" bekamen die Plakettenart "laufend", für die es nie eine
  CSS-Klasse gab - jetzt "success" wie laufende Instanzen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 12:45:03 +02:00

77 lines
2.8 KiB
C#

using System.Text.Json;
namespace ClawdDotNet.Core.Config;
public static class ConfigLoader
{
private static readonly JsonSerializerOptions JsonOptions = new()
{
ReadCommentHandling = JsonCommentHandling.Skip,
AllowTrailingCommas = true,
PropertyNameCaseInsensitive = true
};
public static async Task<InstanceConfig> LoadAsync(string filePath, CancellationToken ct = default)
{
if (!File.Exists(filePath))
throw new FileNotFoundException($"Config file not found: {filePath}");
await using var stream = File.OpenRead(filePath);
var config = await JsonSerializer.DeserializeAsync<InstanceConfig>(stream, JsonOptions, ct)
?? throw new InvalidOperationException($"Config file is empty or invalid: {filePath}");
Migrate(config);
// Make encrypted credentials readable for the runtime. Plain text from
// older configurations stays unchanged and is taken over when saving.
Security.ConfigSecrets.Unprotect(config);
Validate(config, filePath);
return config;
}
/// <summary>
/// Lifts old configurations to the current schema.
///
/// "maxTokens" used to be understood as a context limit, but counted cumulatively
/// across all steps — normal runs therefore aborted prematurely. The value is
/// not taken over, but replaced by the generous default for the cost budget;
/// context control is taken over by maxContextTokens.
/// </summary>
public static void Migrate(InstanceConfig config)
{
foreach (var agent in config.Agents)
Migrate(agent);
}
/// <summary>
/// Migration for an individually loaded agent (AgentSettings.json).
/// </summary>
public static void Migrate(AgentConfig agent)
{
var guard = agent.LoopGuard;
if (guard.LegacyMaxTokens is not { } legacy)
return;
// Only take over if deliberately configured more generously than the default.
if (legacy > guard.MaxCumulativeTokens)
guard.MaxCumulativeTokens = legacy;
guard.LegacyMaxTokens = null;
}
private static void Validate(InstanceConfig config, string filePath)
{
if (string.IsNullOrWhiteSpace(config.OpenRouterApiKey))
throw new InvalidOperationException($"'openRouterApiKey' is required in {filePath}");
var agentIds = new HashSet<string>();
foreach (var agent in config.Agents)
{
if (string.IsNullOrWhiteSpace(agent.AgentId))
throw new InvalidOperationException($"Every agent must have an 'agentId' in {filePath}");
if (!agentIds.Add(agent.AgentId))
throw new InvalidOperationException($"Duplicate agentId '{agent.AgentId}' in {filePath}");
}
}
}