* agent: add Devin — model via agent.model in config.json, subscription via devin acp over ACP * gateway: surface devin acp read errors to waiters * providers: fix devin sign-in exchange; account editor and picker fixes * gateway: scope the devin deny rules and copy credentials when a link can't be made * gateway: note devin never rewrites credentials.toml during a session
81 lines
2.4 KiB
Go
81 lines
2.4 KiB
Go
package agent
|
|
|
|
// Devin keeps its settings in ~/.config/devin/config.json (%APPDATA%\devin on
|
|
// Windows), the model under agent.model. Devin has no endpoint magpie can
|
|
// stand in for — its models are its own — so the options are the families
|
|
// and variants `devin models list --format json` reports, not the magpie
|
|
// catalog. A family id (swe-2, claude-opus-5-5) follows the family's newest;
|
|
// a variant id (swe-2-max) pins one.
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"path/filepath"
|
|
"runtime"
|
|
|
|
"github.com/yetone/magpie/internal/provider"
|
|
)
|
|
|
|
func devin(home, cfg string) *Agent {
|
|
dir := filepath.Join(cfg, "devin")
|
|
if runtime.GOOS == "windows" {
|
|
if app := os.Getenv("APPDATA"); app != "" {
|
|
dir = filepath.Join(app, "devin")
|
|
}
|
|
}
|
|
path := filepath.Join(dir, "config.json")
|
|
return &Agent{
|
|
ID: "devin", Name: "Devin", Icon: "devin",
|
|
Bin: "devin", Dir: dir, Path: path,
|
|
Notice: func() string {
|
|
if Running(`(^|/)devin( |$)`) {
|
|
return "Devin reads its model at start-up — restart open Devin sessions to use this."
|
|
}
|
|
return ""
|
|
},
|
|
Fields: []Field{{
|
|
Key: "model", Label: "model",
|
|
Get: jsonGet(path, "agent.model"),
|
|
Set: jsonSet(path, "agent.model"),
|
|
Options: func(cur map[string]string) []Option {
|
|
return devinOptions(cur["model"])
|
|
},
|
|
}},
|
|
}
|
|
}
|
|
|
|
// providerDevinFamilies lists Devin's models; a var so tests can fake it.
|
|
var providerDevinFamilies = func(ctx context.Context) ([]provider.DevinFamily, error) {
|
|
return provider.DevinFamilies(ctx)
|
|
}
|
|
|
|
// devinOptions offers the families first (a pick that follows the family's
|
|
// newest model), then every variant under its family. They are Devin's own
|
|
// models, not borrowed ones, so like every agent's own list they sit flat —
|
|
// a group each would only crowd the picker's rail with one icon per family.
|
|
func devinOptions(cur string) []Option {
|
|
families, err := providerDevinFamilies(context.Background())
|
|
if err != nil {
|
|
if cur == "" {
|
|
return nil
|
|
}
|
|
return []Option{{Value: cur}}
|
|
}
|
|
out := make([]Option, 0, len(families))
|
|
for _, f := range families {
|
|
out = append(out, Option{Value: f.UID, Label: f.Label, Note: "the family's newest", Icon: "devin"})
|
|
for _, m := range f.Models {
|
|
out = append(out, Option{Value: m.ID, Label: m.Name, Icon: "devin"})
|
|
}
|
|
}
|
|
if cur != "" {
|
|
for _, o := range out {
|
|
if o.Value == cur {
|
|
return out
|
|
}
|
|
}
|
|
out = append([]Option{{Value: cur, Note: "current"}}, out...)
|
|
}
|
|
return out
|
|
}
|