Files
magpie/internal/agent/omp.go
T

157 lines
5.0 KiB
Go

package agent
// omp (oh-my-pi, a fork of Pi) keeps its settings in ~/.omp/agent/config.yml,
// the model of each role under modelRoles as "provider/model", and providers
// of the user's own in models.yml beside it. magpie adds itself there as the
// provider "magpie", keyless (auth: none), with the catalog as its models; a
// model through magpie is "magpie/<provider>/<model>", which omp matches
// whole against provider/id.
import (
"os"
"path/filepath"
"slices"
"strings"
"github.com/yetone/magpie/internal/edit"
)
// ompEfforts are the thinking levels omp knows.
var ompEfforts = []string{"minimal", "low", "medium", "high", "xhigh", "max"}
func omp(home string) *Agent {
dir := filepath.Join(home, ".omp", "agent")
// omp reads the .yml and falls back to the .yaml
pick := func(name string) string {
yml := filepath.Join(dir, name+".yml")
if _, err := os.Stat(yml); err != nil {
if _, err := os.Stat(filepath.Join(dir, name+".yaml")); err == nil {
return filepath.Join(dir, name+".yaml")
}
}
return yml
}
path := pick("config")
get := func() string { v, _ := edit.GetYAML(path, "modelRoles.default"); return v }
dropMagpie := func() error {
// another role may still go through magpie
for _, v := range edit.GetYAMLMap(path, "modelRoles") {
if usesMagpie(v) {
return nil
}
}
return edit.DelYAML(pick("models"), "providers."+magpieID)
}
writeMagpie := func() error {
models := pick("models")
if _, err := os.Stat(models); err != nil {
// omp moves an older models.json to models.yml only while there is
// no models.yml, so one magpie writes first has to carry it over
if err := edit.JSONToYAML(filepath.Join(dir, "models.json"), models); err != nil {
return err
}
}
return edit.SetYAML(models, edit.KV{Path: "providers." + magpieID, Value: ompProvider()})
}
return &Agent{
ID: "omp", Name: "omp", Icon: "omp", Aliases: []string{"oh-my-pi"},
UA: []string{"oh-my-pi"},
Bin: "omp", Dir: dir, Path: path,
Sync: func() error {
return syncYAML(pick("models"), "providers."+magpieID, func() any { return ompProvider() })
},
Notice: func() string {
if Running(`(^|/)omp( |$)`, `@oh-my-pi/pi-coding-agent`) {
return "omp reads its settings at start-up — restart open omp sessions to use this."
}
return ""
},
Check: func() string {
if !usesMagpie(get()) {
return ""
}
models := pick("models")
return wiringOff("omp", models, func(k string) (string, bool) { return edit.GetYAML(models, "providers."+magpieID+"."+k) },
"baseUrl", gatewayV1())
},
Fields: []Field{{
Key: "model", Label: "model",
Get: get,
Set: func(v string) error {
if v == "" {
if err := edit.DelYAML(path, "modelRoles.default"); err != nil {
return err
}
return dropMagpie()
}
if ref, ok := strings.CutPrefix(v, magpieID+"/"); ok && isMagpie(ref) {
if err := writeMagpie(); err != nil {
return err
}
return edit.SetYAML(path, edit.KV{Path: "modelRoles.default", Value: v})
}
if err := edit.SetYAML(path, edit.KV{Path: "modelRoles.default", Value: v}); err != nil {
return err
}
return dropMagpie()
},
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...) },
}},
}
}
type ompModel struct {
ID string `yaml:"id"`
Name string `yaml:"name,omitempty"`
Reasoning bool `yaml:"reasoning"`
Thinking *ompThinking `yaml:"thinking,omitempty"`
Context int `yaml:"contextWindow,omitempty"`
}
type ompThinking struct {
Mode string `yaml:"mode"`
Efforts []string `yaml:"efforts"`
}
type ompProviderEntry struct {
BaseURL string `yaml:"baseUrl"`
API string `yaml:"api"`
Auth string `yaml:"auth"`
Models []ompModel `yaml:"models"`
}
// ompProvider is magpie's entry in models.yml. The thinking efforts are the
// levels omp offers for the model; it sends them as reasoning_effort.
func ompProvider() ompProviderEntry {
ms := []ompModel{}
for _, m := range magpieModels("omp") {
e := ompModel{ID: m.ID, Name: m.Name, Context: m.Context}
var efforts []string
for _, x := range ompEfforts { // in omp's order
if slices.Contains(m.Efforts, x) {
efforts = append(efforts, x)
}
}
if len(efforts) > 0 {
e.Reasoning = true
e.Thinking = &ompThinking{Mode: "effort", Efforts: efforts}
}
ms = append(ms, e)
}
return ompProviderEntry{BaseURL: gatewayV1(), API: "openai-completions", Auth: "none", Models: ms}
}