agent: an agent whose own config keeps the reasoning effort its sessions start with gets an effort field, as Codex, Grok and Pi have — Claude Code's effortLevel (low to xhigh; max lasts a session only), Hermes's agent.reasoning_effort, omp's defaultThinkingLevel, Goose's GOOSE_THINKING_EFFORT, Copilot's effortLevel (the model's levels when its list says), Crush's large model reasoning_effort, Command Code's reasoningEffort for the current model (it keeps one per model) and dsh's llm-deepseek reasoningEffort (off, high or max, kept when the model changes or the catalog syncs); OpenCode, Gemini CLI, Cursor, ZCode, Devin and Alma keep none of their own in a file, so they get none. Asked on Discord
This commit is contained in:
8 files changed
+430
-7
No files matched your search
@@ -419,6 +419,20 @@ func goose(home, cfg string) *Agent {
|
||||
Options: func(cur map[string]string) []Option {
|
||||
return ownOptions("", cur["model"], "anthropic", "openai", "google", "openrouter")
|
||||
},
|
||||
}, {
|
||||
// GOOSE_THINKING_EFFORT, the effort goose asks of a model that
|
||||
// thinks, for every provider
|
||||
Key: "effort", Label: "effort",
|
||||
Get: func() string { v, _ := get("GOOSE_THINKING_EFFORT"); return v },
|
||||
Set: func(v string) error {
|
||||
if v == "" {
|
||||
return edit.DelYAMLTop(path, "GOOSE_THINKING_EFFORT")
|
||||
}
|
||||
return set(edit.KV{Path: "GOOSE_THINKING_EFFORT", Value: v})
|
||||
},
|
||||
Options: func(map[string]string) []Option {
|
||||
return static("off", "low", "medium", "high", "max")
|
||||
},
|
||||
}},
|
||||
}
|
||||
}
|
||||
@@ -470,6 +484,21 @@ func copilot(home string) *Agent {
|
||||
}
|
||||
return out
|
||||
},
|
||||
}, {
|
||||
// effortLevel, which Copilot saves beside the model and clears
|
||||
// when its own /model changes the model; the levels are the
|
||||
// model's, low to xhigh when Copilot's list does not say
|
||||
Key: "effort", Label: "effort",
|
||||
Get: jsonGet(path, "effortLevel"),
|
||||
Set: jsonSet(path, "effortLevel"),
|
||||
Options: func(cur map[string]string) []Option {
|
||||
if live, _, ok := catalog.Live("copilot"); ok {
|
||||
if e := catalog.Efforts(live, cur["model"]); len(e) > 0 {
|
||||
return static(e...)
|
||||
}
|
||||
}
|
||||
return static("low", "medium", "high", "xhigh")
|
||||
},
|
||||
}},
|
||||
}
|
||||
}
|
||||
@@ -541,6 +570,22 @@ func crush(home, cfg string) *Agent {
|
||||
Fields: []Field{
|
||||
{Key: "model", Label: "large", Get: pairGet(get, "models.large.provider", "models.large.model"), Set: setter("models.large.provider", "models.large.model"), Options: opts("model")},
|
||||
{Key: "small", Label: "small", Get: pairGet(get, "models.small.provider", "models.small.model"), Set: setter("models.small.provider", "models.small.model"), Options: opts("small")},
|
||||
{
|
||||
// the large model's reasoning_effort, which Crush's schema
|
||||
// takes as low, medium or high (for OpenAI-style models)
|
||||
Key: "effort", Label: "effort",
|
||||
Get: func() string { v, _ := get("models.large.reasoning_effort"); return v },
|
||||
Set: func(v string) error {
|
||||
if v == "" {
|
||||
return edit.DelJSON(path, "models.large.reasoning_effort")
|
||||
}
|
||||
if m, _ := get("models.large.model"); m == "" {
|
||||
return fmt.Errorf("pick Crush's large model first; the effort is kept with it")
|
||||
}
|
||||
return set(edit.KV{Path: "models.large.reasoning_effort", Value: v})
|
||||
},
|
||||
Options: func(map[string]string) []Option { return static("low", "medium", "high") },
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -30,6 +30,9 @@ var claudeEnv = []string{
|
||||
// A tier that has none follows the main model.
|
||||
var claudeTiers = []string{"opus", "sonnet", "haiku", "fable"}
|
||||
|
||||
// claudeEfforts are the levels settings.json's effortLevel takes.
|
||||
var claudeEfforts = []string{"low", "medium", "high", "xhigh"}
|
||||
|
||||
func tierEnv(tier string) string { return "ANTHROPIC_DEFAULT_" + strings.ToUpper(tier) + "_MODEL" }
|
||||
|
||||
func claude(home string) *Agent {
|
||||
@@ -148,6 +151,20 @@ func claude(home string) *Agent {
|
||||
// go stale.
|
||||
return append(group(name, own), claudeViaMagpie()...)
|
||||
},
|
||||
}, {
|
||||
// the effort Claude Code starts with, as its /effort saves it;
|
||||
// settings.json keeps low to xhigh (max lasts a session only)
|
||||
Key: "effort", Label: "effort",
|
||||
Get: jsonGet(path, "effortLevel"),
|
||||
Set: func(v string) error {
|
||||
if v != "" && !contains(claudeEfforts, v) {
|
||||
return fmt.Errorf("Claude Code keeps an effort of %s, not %q", strings.Join(claudeEfforts, ", "), v)
|
||||
}
|
||||
return jsonSet(path, "effortLevel")(v)
|
||||
},
|
||||
Options: func(map[string]string) []Option {
|
||||
return static(claudeEfforts...)
|
||||
},
|
||||
}}
|
||||
for _, tier := range claudeTiers {
|
||||
fields = append(fields, Field{
|
||||
|
||||
@@ -8,6 +8,8 @@ package agent
|
||||
// at the first "/". Command Code still wants its own sign-in for these.
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
@@ -91,6 +93,47 @@ func commandCode(home string) *Agent {
|
||||
}
|
||||
return append(out, viaMagpie("commandcode", magpieID+"/")...)
|
||||
},
|
||||
}, {
|
||||
// settings.json's reasoningEffort keeps an effort for each model
|
||||
// (Command Code's /effort saves it); this is the current model's
|
||||
Key: "effort", Label: "effort",
|
||||
Get: func() string { return ccEffortMap(path)[get("model")] },
|
||||
Set: func(v string) error {
|
||||
model := get("model")
|
||||
if model == "" {
|
||||
return fmt.Errorf("pick Command Code's model first; it keeps an effort for each model")
|
||||
}
|
||||
efforts := ccEffortMap(path)
|
||||
if v == "" {
|
||||
delete(efforts, model)
|
||||
} else {
|
||||
efforts[model] = v
|
||||
}
|
||||
if len(efforts) == 0 {
|
||||
return edit.DelJSON(path, "reasoningEffort")
|
||||
}
|
||||
// written whole, as model ids hold dots
|
||||
return edit.SetJSON(path, edit.KV{Path: "reasoningEffort", Value: efforts})
|
||||
},
|
||||
Options: func(cur map[string]string) []Option {
|
||||
if ref, ok := strings.CutPrefix(cur["model"], magpieID+"/"); ok {
|
||||
for _, m := range magpieModels("commandcode") {
|
||||
if m.ID != ref {
|
||||
continue
|
||||
}
|
||||
var efforts []string
|
||||
for _, x := range ccEfforts {
|
||||
if slices.Contains(m.Efforts, x) {
|
||||
efforts = append(efforts, x)
|
||||
}
|
||||
}
|
||||
if len(efforts) > 0 {
|
||||
return static(efforts...)
|
||||
}
|
||||
}
|
||||
}
|
||||
return static(ccEfforts...)
|
||||
},
|
||||
}},
|
||||
}
|
||||
}
|
||||
@@ -115,3 +158,24 @@ func ccProviderJSON() any {
|
||||
}
|
||||
return map[string]any{"name": "magpie", "api": "openai-completions", "baseURL": gatewayV1(), "apiKey": false, "models": ms}
|
||||
}
|
||||
|
||||
// ccEffortMap is settings.json's reasoningEffort, model id to effort.
|
||||
func ccEffortMap(path string) map[string]string {
|
||||
out := map[string]string{}
|
||||
b, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return out
|
||||
}
|
||||
var c struct {
|
||||
Efforts map[string]any `json:"reasoningEffort"`
|
||||
}
|
||||
if json.Unmarshal(b, &c) != nil {
|
||||
return out
|
||||
}
|
||||
for k, v := range c.Efforts {
|
||||
if s, ok := v.(string); ok {
|
||||
out[k] = s
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
+90
-6
@@ -74,6 +74,13 @@ func dsh(home string) *Agent {
|
||||
Options: func(map[string]string) []Option {
|
||||
return append(append([]Option{}, dshModels...), viaMagpie("dsh", magpieID+"/")...)
|
||||
},
|
||||
}, {
|
||||
// llm-deepseek's reasoningEffort, the thinking effort sessions
|
||||
// start with (off, high or max; dsh's own default is high)
|
||||
Key: "effort", Label: "thinking",
|
||||
Get: func() string { return dshGetEffort(dir) },
|
||||
Set: func(v string) error { return dshSetEffort(dir, v) },
|
||||
Options: func(map[string]string) []Option { return static(dshEfforts...) },
|
||||
}},
|
||||
}
|
||||
}
|
||||
@@ -320,13 +327,13 @@ func dshSetFile(path, v string, modern bool) error {
|
||||
drop("agent-loop")
|
||||
drop("llm-deepseek")
|
||||
case modern && viaGateway:
|
||||
put("llm-deepseek", dshProviderLines(true))
|
||||
put("llm-deepseek", dshProviderLines(true, dshEffortIn(items)))
|
||||
put("agent-default-model", dshDefaultLines(ref))
|
||||
case modern:
|
||||
drop("llm-deepseek")
|
||||
put("agent-default-model", dshDefaultLines(v))
|
||||
case viaGateway:
|
||||
put("llm-deepseek", dshProviderLines(false))
|
||||
put("llm-deepseek", dshProviderLines(false, dshEffortIn(items)))
|
||||
put("agent-loop", dshLoopLines(ref))
|
||||
put("api-gateway", dshRouteLines(ref))
|
||||
default:
|
||||
@@ -356,19 +363,23 @@ func yamlQuote(s string) string {
|
||||
|
||||
// dshProviderLines is the llm-deepseek entry pointing dsh at the gateway,
|
||||
// with the catalog as the models its /model offers. Since 0.1.5 the key is a
|
||||
// credential the entry names rather than holds.
|
||||
func dshProviderLines(modern bool) []string {
|
||||
// credential the entry names rather than holds. effort is the thinking
|
||||
// effort sessions start with, high (dsh's own default) when "".
|
||||
func dshProviderLines(modern bool, effort string) []string {
|
||||
key := " apiKey: " + yamlQuote(gateway.Token)
|
||||
if modern {
|
||||
key = " apiKeyEnv: " + dshKeyRef
|
||||
}
|
||||
if effort == "" {
|
||||
effort = "high"
|
||||
}
|
||||
lines := []string{
|
||||
"- id: llm-deepseek " + dshMark,
|
||||
" config:",
|
||||
key,
|
||||
" baseURL: " + yamlQuote(gatewayV1()),
|
||||
" thinking: enabled",
|
||||
" reasoningEffort: high",
|
||||
" reasoningEffort: " + effort,
|
||||
" models:",
|
||||
}
|
||||
ms := magpieModels("dsh")
|
||||
@@ -447,7 +458,7 @@ func dshSync(dir string) error {
|
||||
for _, l := range items[i].lines {
|
||||
modern = modern || strings.HasPrefix(strings.TrimSpace(l), "apiKeyEnv:")
|
||||
}
|
||||
lines := dshProviderLines(modern)
|
||||
lines := dshProviderLines(modern, dshEffortIn(items))
|
||||
if strings.Join(lines, "\n") == strings.Join(items[i].lines, "\n") {
|
||||
continue
|
||||
}
|
||||
@@ -463,6 +474,79 @@ func dshSync(dir string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// dshEfforts are the thinking efforts llm-deepseek takes.
|
||||
var dshEfforts = []string{"off", "high", "max"}
|
||||
|
||||
// dshEffortIn is the reasoningEffort of the llm-deepseek entry magpie
|
||||
// wrote, "" when there is none.
|
||||
func dshEffortIn(items []dshItem) string {
|
||||
i := dshFind(items, "llm-deepseek")
|
||||
if i < 0 || !items[i].magpie {
|
||||
return ""
|
||||
}
|
||||
for _, l := range items[i].lines {
|
||||
if k, v, ok := strings.Cut(strings.TrimSpace(l), ":"); ok && k == "reasoningEffort" {
|
||||
return yamlScalar(strings.TrimSpace(v))
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// dshFiles are the patch lists magpie writes: every profile's, else
|
||||
// config.yaml.
|
||||
func dshFiles(dir string) []string {
|
||||
if files := dshProfiles(dir); len(files) > 0 {
|
||||
return files
|
||||
}
|
||||
return []string{filepath.Join(dir, "config.yaml")}
|
||||
}
|
||||
|
||||
// dshGetEffort reads the effort of magpie's llm-deepseek entry.
|
||||
func dshGetEffort(dir string) string {
|
||||
_, items, err := dshRead(dshFiles(dir)[0])
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return dshEffortIn(items)
|
||||
}
|
||||
|
||||
// dshSetEffort writes v into each llm-deepseek entry magpie wrote. dsh's
|
||||
// own row is used whole when there is none, so the effort goes with a
|
||||
// model through magpie.
|
||||
func dshSetEffort(dir, v string) error {
|
||||
if v != "" && !contains(dshEfforts, v) {
|
||||
return fmt.Errorf("DeepSeek Harness takes an effort of %s, not %q", strings.Join(dshEfforts, ", "), v)
|
||||
}
|
||||
done := false
|
||||
for _, f := range dshFiles(dir) {
|
||||
head, items, err := dshRead(f)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
i := dshFind(items, "llm-deepseek")
|
||||
if i < 0 || !items[i].magpie {
|
||||
continue
|
||||
}
|
||||
modern := false
|
||||
for _, l := range items[i].lines {
|
||||
modern = modern || strings.HasPrefix(strings.TrimSpace(l), "apiKeyEnv:")
|
||||
}
|
||||
items[i].lines = dshProviderLines(modern, v)
|
||||
out := append([]string{}, head...)
|
||||
for _, it := range items {
|
||||
out = append(out, it.lines...)
|
||||
}
|
||||
if err := edit.WriteAtomic(f, []byte(strings.Join(out, "\n")+"\n")); err != nil {
|
||||
return err
|
||||
}
|
||||
done = true
|
||||
}
|
||||
if !done && v != "" {
|
||||
return fmt.Errorf("pick a model through magpie for DeepSeek Harness first; the effort is kept with magpie's DeepSeek entry")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// dshSettingsEndpoint reports whether dsh's own settings carry an
|
||||
// llm-deepseek section with an endpoint or key, which dsh puts over the
|
||||
// patch list.
|
||||
|
||||
@@ -185,7 +185,7 @@ func TestDshModelLimits(t *testing.T) {
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
s := strings.Join(dshProviderLines(true), "\n")
|
||||
s := strings.Join(dshProviderLines(true, ""), "\n")
|
||||
want := ` - id: "v/see"
|
||||
name: "see · V"
|
||||
contextWindow: 400000
|
||||
|
||||
@@ -0,0 +1,187 @@
|
||||
package agent
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/yetone/magpie/internal/edit"
|
||||
"github.com/yetone/magpie/internal/provider"
|
||||
)
|
||||
|
||||
// effortHome is a sandbox HOME with a provider for magpie to serve.
|
||||
func effortHome(t *testing.T) string {
|
||||
t.Helper()
|
||||
home := t.TempDir()
|
||||
t.Setenv("HOME", home)
|
||||
t.Setenv("XDG_CONFIG_HOME", filepath.Join(home, ".config"))
|
||||
t.Setenv("XDG_CACHE_HOME", filepath.Join(home, ".cache"))
|
||||
t.Setenv("HERMES_HOME", "")
|
||||
t.Setenv("DSH_HOME", "")
|
||||
if err := provider.Save(provider.Provider{ID: "deepseek", Name: "DeepSeek", Chat: "https://api.deepseek.com/v1", Key: "k", Models: []string{"pro", "flash"}}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return home
|
||||
}
|
||||
|
||||
// setEffort sets, reads back and resets an agent's effort, checking what
|
||||
// else is in its file stays.
|
||||
func setEffort(t *testing.T, a *Agent, v, keep string) {
|
||||
t.Helper()
|
||||
f := a.Field("effort")
|
||||
if f == nil {
|
||||
t.Fatalf("%s has no effort field", a.ID)
|
||||
}
|
||||
if err := f.Set(v); err != nil {
|
||||
t.Fatalf("%s: %v", a.ID, err)
|
||||
}
|
||||
if got := f.Get(); got != v {
|
||||
t.Fatalf("%s: effort %q, want %q\n%s", a.ID, got, v, readFile(a.Path))
|
||||
}
|
||||
found := false
|
||||
for _, o := range f.Options(map[string]string{}) {
|
||||
found = found || o.Value == v
|
||||
}
|
||||
if !found {
|
||||
t.Fatalf("%s: %q not among the options", a.ID, v)
|
||||
}
|
||||
if !strings.Contains(readFile(a.Path), keep) {
|
||||
t.Fatalf("%s lost %q:\n%s", a.ID, keep, readFile(a.Path))
|
||||
}
|
||||
if err := f.Set(""); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got := f.Get(); got != "" {
|
||||
t.Fatalf("%s: reset left %q\n%s", a.ID, got, readFile(a.Path))
|
||||
}
|
||||
if !strings.Contains(readFile(a.Path), keep) {
|
||||
t.Fatalf("%s lost %q on reset:\n%s", a.ID, keep, readFile(a.Path))
|
||||
}
|
||||
}
|
||||
|
||||
func TestEffortFields(t *testing.T) {
|
||||
home := effortHome(t)
|
||||
cfg := filepath.Join(home, ".config")
|
||||
|
||||
writeFile(t, filepath.Join(home, ".claude", "settings.json"), `{"theme":"dark","model":"opus"}`)
|
||||
c := claude(home)
|
||||
setEffort(t, c, "xhigh", `"theme"`)
|
||||
// max lasts a session in Claude Code; settings.json drops it
|
||||
if err := c.Field("effort").Set("max"); err == nil {
|
||||
t.Fatal("claude took max")
|
||||
}
|
||||
|
||||
writeFile(t, filepath.Join(home, ".hermes", "config.yaml"), "model:\n default: x\nagent:\n max_turns: 9\n")
|
||||
h := hermes(home)
|
||||
setEffort(t, h, "none", "max_turns: 9")
|
||||
if v, _ := edit.GetYAML(h.Path, "model.default"); v != "x" {
|
||||
t.Fatalf("hermes model: %q", v)
|
||||
}
|
||||
|
||||
writeFile(t, filepath.Join(home, ".omp", "agent", "config.yml"), "modelRoles:\n default: a/b\n")
|
||||
setEffort(t, omp(home), "xhigh", "default: a/b")
|
||||
|
||||
writeFile(t, filepath.Join(cfg, "goose", "config.yaml"), "GOOSE_MODEL: m\nGOOSE_PROVIDER: p\n")
|
||||
g := goose(home, cfg)
|
||||
setEffort(t, g, "max", "GOOSE_MODEL: m")
|
||||
if v, _ := edit.GetYAMLTop(g.Path, "GOOSE_PROVIDER"); v != "p" {
|
||||
t.Fatalf("goose provider: %q", v)
|
||||
}
|
||||
|
||||
writeFile(t, filepath.Join(home, ".copilot", "settings.json"), `{"model":"gpt-5.5","contextTier":"long"}`)
|
||||
setEffort(t, copilot(home), "xhigh", `"contextTier"`)
|
||||
|
||||
cr := crush(home, cfg)
|
||||
os.MkdirAll(filepath.Dir(cr.Path), 0o755)
|
||||
os.WriteFile(cr.Path, []byte(`{"options":{"debug":true}}`), 0o644)
|
||||
// Crush keeps the effort with the large model
|
||||
if err := cr.Field("effort").Set("high"); err == nil {
|
||||
t.Fatal("crush took an effort without a large model")
|
||||
}
|
||||
if err := cr.Field("model").Set("magpie/deepseek/pro"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
setEffort(t, cr, "medium", `"debug"`)
|
||||
if cr.Field("model").Get() != "magpie/deepseek/pro" {
|
||||
t.Fatalf("crush model: %s", readFile(cr.Path))
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommandCodeEffort(t *testing.T) {
|
||||
home := effortHome(t)
|
||||
path := filepath.Join(home, ".commandcode", "settings.json")
|
||||
writeFile(t, path, `{"theme":"dark","reasoningEffort":{"gpt-5.5":"low"}}`)
|
||||
a := commandCode(home)
|
||||
f := a.Field("effort")
|
||||
if err := f.Set("high"); err == nil {
|
||||
t.Fatal("took an effort without a model")
|
||||
}
|
||||
if err := a.Field("model").Set("magpie/deepseek/pro"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
setEffort(t, a, "max", `"theme"`)
|
||||
// each model keeps its own; the other one's stays
|
||||
if m := ccEffortMap(path); m["gpt-5.5"] != "low" || len(m) != 1 {
|
||||
t.Fatalf("efforts: %v\n%s", m, readFile(path))
|
||||
}
|
||||
// a model id with dots is one key, not a path
|
||||
if err := a.Field("model").Set("anthropic/claude-4.5"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := f.Set("xhigh"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if m := ccEffortMap(path); m["anthropic/claude-4.5"] != "xhigh" || m["gpt-5.5"] != "low" {
|
||||
t.Fatalf("efforts: %v\n%s", m, readFile(path))
|
||||
}
|
||||
// the last one gone, the map goes too
|
||||
os.WriteFile(path, []byte(`{"model":"x","reasoningEffort":{"x":"low"}}`), 0o644)
|
||||
if err := f.Set(""); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if strings.Contains(readFile(path), "reasoningEffort") {
|
||||
t.Fatalf("left the map:\n%s", readFile(path))
|
||||
}
|
||||
}
|
||||
|
||||
func TestDshEffort(t *testing.T) {
|
||||
home := effortHome(t)
|
||||
patch := filepath.Join(home, ".dsh", "profiles", "web", "cordis.patch.yml")
|
||||
writeFile(t, patch, "[]\n")
|
||||
a := dsh(home)
|
||||
f := a.Field("effort")
|
||||
// dsh's own row is used whole; the effort goes with magpie's
|
||||
if err := f.Set("max"); err == nil {
|
||||
t.Fatal("took an effort on dsh's own row")
|
||||
}
|
||||
if err := a.Field("model").Set("magpie/deepseek/pro"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if f.Get() != "high" {
|
||||
t.Fatalf("effort: %q\n%s", f.Get(), readFile(patch))
|
||||
}
|
||||
if err := f.Set("max"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := f.Set("low"); err == nil {
|
||||
t.Fatal("took low")
|
||||
}
|
||||
// kept when the model changes and when the catalog is synced
|
||||
if err := a.Field("model").Set("magpie/deepseek/flash"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := a.Sync(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
raw := readFile(patch)
|
||||
if f.Get() != "max" || strings.Count(raw, "reasoningEffort:") != 1 || !strings.Contains(raw, `model: "deepseek/flash"`) {
|
||||
t.Fatalf("patch:\n%s", raw)
|
||||
}
|
||||
if err := f.Set(""); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if f.Get() != "high" {
|
||||
t.Fatalf("reset: %q", f.Get())
|
||||
}
|
||||
}
|
||||
@@ -108,6 +108,20 @@ func hermes(home string) *Agent {
|
||||
Options: func(cur map[string]string) []Option {
|
||||
return append(ownOptions("", cur["model"]), viaMagpie("hermes", magpieID+"/")...)
|
||||
},
|
||||
}, {
|
||||
// agent.reasoning_effort, which Hermes's /reasoning saves; none
|
||||
// turns reasoning off, and unset Hermes asks for medium
|
||||
Key: "effort", Label: "effort",
|
||||
Get: func() string { return getKey("agent.reasoning_effort") },
|
||||
Set: func(v string) error {
|
||||
if v == "" {
|
||||
return edit.DelYAML(path, "agent.reasoning_effort")
|
||||
}
|
||||
return edit.SetYAML(path, edit.KV{Path: "agent.reasoning_effort", Value: v})
|
||||
},
|
||||
Options: func(map[string]string) []Option {
|
||||
return static("none", "minimal", "low", "medium", "high", "xhigh")
|
||||
},
|
||||
}},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,6 +98,18 @@ func omp(home string) *Agent {
|
||||
Options: func(cur map[string]string) []Option {
|
||||
return append(ownOptions("", cur["model"]), viaMagpie("omp", magpieID+"/")...)
|
||||
},
|
||||
}, {
|
||||
// the thinking level sessions start with, as omp's settings save
|
||||
// it; unset omp takes high (it also knows auto, its own pick)
|
||||
Key: "effort", Label: "thinking",
|
||||
Get: func() string { v, _ := edit.GetYAML(path, "defaultThinkingLevel"); return v },
|
||||
Set: func(v string) error {
|
||||
if v == "" {
|
||||
return edit.DelYAML(path, "defaultThinkingLevel")
|
||||
}
|
||||
return edit.SetYAML(path, edit.KV{Path: "defaultThinkingLevel", Value: v})
|
||||
},
|
||||
Options: func(map[string]string) []Option { return static(ompEfforts...) },
|
||||
}},
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user