feat(settings): per-model effort and turn presets
ClaudeDo never passed --effort, so every session inherited whatever effortLevel the user's Claude Code config happened to carry. Settings -> General now holds one row per model alias (haiku medium/20, sonnet high/30, opus high/40, fable high/25) supplying the global effort and turn defaults; list- and task-level max-turns overrides still win, and the agent editor's inherited badge follows the model. --effort is applied to autonomous runs and to every ConPTY spec (task session, planning start/resume, ad-hoc, list handler). The model itself is deliberately not forced on interactive sessions. The single global 'Max turns' field is replaced by the table, and 'fable' joins ModelRegistry.Aliases. The migration also adds the is_manual columns used by the next commit.
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
using ClaudeDo.Data.Models;
|
||||
|
||||
namespace ClaudeDo.Data.Tests;
|
||||
|
||||
public class ModelPresetsTests
|
||||
{
|
||||
[Fact]
|
||||
public void Defaults_cover_every_known_alias()
|
||||
{
|
||||
Assert.Equal(
|
||||
ModelRegistry.Aliases.OrderBy(a => a),
|
||||
ModelPresets.Defaults.Select(p => p.Model).OrderBy(m => m));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(null)]
|
||||
[InlineData("")]
|
||||
[InlineData(" ")]
|
||||
[InlineData("not json at all")]
|
||||
public void Parse_falls_back_to_defaults_when_unusable(string? json)
|
||||
{
|
||||
Assert.Equal(ModelPresets.Defaults, ModelPresets.Parse(json));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Serialize_roundtrips()
|
||||
{
|
||||
var edited = new[] { new ModelPreset("opus", "max", 55) };
|
||||
var parsed = ModelPresets.Parse(ModelPresets.Serialize(edited));
|
||||
|
||||
Assert.Equal("max", ModelPresets.For(parsed, "opus").Effort);
|
||||
Assert.Equal(55, ModelPresets.For(parsed, "opus").MaxTurns);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Serialize_fills_missing_aliases_from_defaults()
|
||||
{
|
||||
var parsed = ModelPresets.Parse(ModelPresets.Serialize(new[] { new ModelPreset("opus", "max", 55) }));
|
||||
|
||||
Assert.Equal(ModelRegistry.Aliases.Count, parsed.Count);
|
||||
Assert.Equal(ModelPresets.For(ModelPresets.Defaults, "haiku"), ModelPresets.For(parsed, "haiku"));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("nonsense", 30)] // unknown effort → the default row's effort
|
||||
[InlineData("high", 0)] // turns out of range → the default row's turns
|
||||
[InlineData("high", 999)]
|
||||
public void Serialize_rejects_out_of_range_values(string effort, int turns)
|
||||
{
|
||||
var fallback = ModelPresets.For(ModelPresets.Defaults, "sonnet");
|
||||
var parsed = ModelPresets.Parse(ModelPresets.Serialize(new[] { new ModelPreset("sonnet", effort, turns) }));
|
||||
var row = ModelPresets.For(parsed, "sonnet");
|
||||
|
||||
if (effort == "nonsense") Assert.Equal(fallback.Effort, row.Effort);
|
||||
if (turns is 0 or 999) Assert.Equal(fallback.MaxTurns, row.MaxTurns);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void For_unknown_model_yields_a_usable_row()
|
||||
{
|
||||
var row = ModelPresets.For(ModelPresets.Defaults, "some-future-model");
|
||||
|
||||
Assert.Equal("some-future-model", row.Model);
|
||||
Assert.Contains(row.Effort, EffortRegistry.Levels);
|
||||
Assert.True(row.MaxTurns > 0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void For_null_model_uses_the_default_alias()
|
||||
{
|
||||
Assert.Equal(
|
||||
ModelPresets.For(ModelPresets.Defaults, ModelRegistry.DefaultAlias),
|
||||
ModelPresets.For(ModelPresets.Defaults, null));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("low", "low")]
|
||||
[InlineData("XHIGH", "xhigh")]
|
||||
[InlineData(" max ", "max")]
|
||||
public void NormalizeLevel_canonicalizes(string input, string expected)
|
||||
{
|
||||
Assert.Equal(expected, EffortRegistry.NormalizeLevel(input));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NormalizeLevel_blank_means_unset_and_unknown_throws()
|
||||
{
|
||||
Assert.Null(EffortRegistry.NormalizeLevel(null));
|
||||
Assert.Throws<ArgumentException>(() => EffortRegistry.NormalizeLevel("gigantic"));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user