110 lines
3.5 KiB
Go
110 lines
3.5 KiB
Go
package agent
|
|
|
|
import (
|
|
"strings"
|
|
|
|
"github.com/yetone/magpie/internal/catalog"
|
|
"github.com/yetone/magpie/internal/gateway"
|
|
"github.com/yetone/magpie/internal/provider"
|
|
)
|
|
|
|
// Every agent's model picker has two halves: the models the agent reaches
|
|
// on its own (its sign-in, its own keys), and magpie's catalog — every model
|
|
// of every provider the user added, reached through the local gateway.
|
|
// A catalog model is spelled "provider/model"; an agent that sends that to
|
|
// the gateway gets the vendor's reply in whichever API the agent speaks.
|
|
|
|
// magpieID is the provider id agents know the gateway by.
|
|
const magpieID = "magpie"
|
|
|
|
// RoutingGroups is the picker's group of routing groups.
|
|
const RoutingGroups = "Routing groups"
|
|
|
|
// viaMagpie lists the catalog as agent is shown it, for a picker: the
|
|
// routing groups first, in one group of their own, then one group per
|
|
// provider.
|
|
func viaMagpie(agent, prefix string) []Option {
|
|
var out, groups []Option
|
|
shown, _ := provider.CatalogFor(agent)
|
|
for _, e := range shown {
|
|
if e.Group != "" {
|
|
groups = append(groups, Option{Value: prefix + e.ID, Label: e.Name, Note: "routing group · via magpie",
|
|
Icon: e.Provider.Icon, Icons: e.Icons, Group: RoutingGroups, Ref: e.ID})
|
|
continue
|
|
}
|
|
note := e.Provider.Name + " · via magpie"
|
|
if a := e.Provider.Account; a != nil {
|
|
note = a.User + " · via magpie"
|
|
}
|
|
out = append(out, Option{Value: prefix + e.ID, Label: e.Name, Note: note,
|
|
Icon: e.Provider.Icon, Group: e.Provider.Name, Ref: e.ID, Free: e.Free, Context: e.Context})
|
|
}
|
|
return append(groups, out...)
|
|
}
|
|
|
|
// viaMagpieFor is viaMagpie without the agent's own account: Codex CLI going
|
|
// through magpie to its own ChatGPT login would only add a hop.
|
|
func viaMagpieFor(agentID, prefix string) []Option {
|
|
var out []Option
|
|
for _, o := range viaMagpie(agentID, prefix) {
|
|
if !strings.HasPrefix(o.Value, prefix+agentID+"/") {
|
|
out = append(out, o)
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
// isMagpie reports whether a model value is a catalog reference.
|
|
func isMagpie(v string) bool {
|
|
_, _, ok := provider.Resolve(v)
|
|
return ok && strings.Contains(v, "/")
|
|
}
|
|
|
|
func firstOf(xs []string) string {
|
|
if len(xs) > 0 {
|
|
return xs[0]
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// magpieModels is the catalog as agent is shown it, as catalog.Models, for
|
|
// agents that keep their own model files.
|
|
func magpieModels(agent string) []catalog.Model {
|
|
var out []catalog.Model
|
|
shown, _ := provider.CatalogFor(agent)
|
|
labels := provider.Labels(shown)
|
|
for i, e := range shown {
|
|
m := catalog.Model{ID: e.ID, Name: labels[i], Provider: firstOf(e.Provider.Catalogs()), Efforts: e.Efforts, Images: e.Images, ImageInput: e.ImageInput, Context: e.Context, Output: e.Output}
|
|
// APIs is the one to ask it on for the gateway to relay the request
|
|
// as it is; none for a group, whose members may each want another
|
|
if e.Group == "" {
|
|
if n := e.Provider.Native(e.Model); n != "" {
|
|
m.APIs = []string{string(n)}
|
|
}
|
|
}
|
|
out = append(out, m)
|
|
}
|
|
return out
|
|
}
|
|
|
|
// maxTokens is the output limit an agent is handed for m, kept within the
|
|
// context window it is handed with it: models.dev lists some models' output
|
|
// above their window (deepseek-chat's 384000 against 128000). An unknown
|
|
// window leaves the output as it is.
|
|
func maxTokens(m catalog.Model) int {
|
|
if m.Context > 0 && m.Output > m.Context {
|
|
return m.Context
|
|
}
|
|
return m.Output
|
|
}
|
|
|
|
// group tags every option with a group name.
|
|
func group(name string, opts []Option) []Option {
|
|
for i := range opts {
|
|
opts[i].Group = name
|
|
}
|
|
return opts
|
|
}
|
|
|
|
func gatewayV1() string { return gateway.URL() + "/v1" }
|