79 lines
2.5 KiB
Go
79 lines
2.5 KiB
Go
package agent
|
|
|
|
import (
|
|
"fmt"
|
|
"sort"
|
|
|
|
"gitea.alexandru.macocian.me/amacocian/sherlock/internal/mcp"
|
|
)
|
|
|
|
// backend captures the per-CLI quirks shared by every agent that speaks
|
|
// to a given coding-assistant CLI: how the rendered MCP-config file is
|
|
// passed on the command line, and which env vars must be scrubbed from
|
|
// the child process.
|
|
//
|
|
// Built-in agents (copilot, claude) and operator-defined custom agents
|
|
// from config.toml both spawn through a backend, so a `copilot-local`
|
|
// wrapper formats its MCP config exactly like the real `copilot`.
|
|
type backend struct {
|
|
// mcpArgs returns the CLI flags that point the agent at the rendered
|
|
// MCP-config file at path.
|
|
mcpArgs func(path string) []string
|
|
// forbidEnv lists env vars stripped from the child (e.g. a personal
|
|
// ANTHROPIC_API_KEY that must not override broker-managed behavior).
|
|
forbidEnv []string
|
|
}
|
|
|
|
// backends is the set of known backend types. The keys are the values
|
|
// operators put in `[agents.<name>].backend`.
|
|
var backends = map[string]backend{
|
|
"copilot": {
|
|
// The `@` prefix tells Copilot to treat the argument as a file
|
|
// path rather than inline JSON.
|
|
mcpArgs: func(p string) []string { return []string{"--additional-mcp-config", "@" + p} },
|
|
},
|
|
"claude": {
|
|
mcpArgs: func(p string) []string { return []string{"--mcp-config", p} },
|
|
forbidEnv: []string{"ANTHROPIC_API_KEY"},
|
|
},
|
|
}
|
|
|
|
// backendNames returns the known backend type names, sorted, for error
|
|
// messages.
|
|
func backendNames() []string {
|
|
out := make([]string, 0, len(backends))
|
|
for n := range backends {
|
|
out = append(out, n)
|
|
}
|
|
sort.Strings(out)
|
|
return out
|
|
}
|
|
|
|
// spawn is the shared spawn path used by both built-in and custom
|
|
// agents. name selects the rendered MCP-config file name; command is the
|
|
// binary to exec. On success it does not return (the process is
|
|
// replaced); any returned error is a setup failure.
|
|
func (b backend) spawn(name, command string, ctx Context, args []string) error {
|
|
bin, err := LookPath(command)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
mcpPath, err := mcp.Render(name, ctx.Servers)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
argv := append([]string{bin}, b.mcpArgs(mcpPath)...)
|
|
argv = append(argv, args...)
|
|
return DefaultExecer.Exec(bin, argv, BuildEnv(b.forbidEnv, nil))
|
|
}
|
|
|
|
// resolveBackend returns the backend for a type name, with a friendly
|
|
// error listing the valid choices when it is unknown.
|
|
func resolveBackend(name string) (backend, error) {
|
|
b, ok := backends[name]
|
|
if !ok {
|
|
return backend{}, fmt.Errorf("unknown backend %q (want one of: %v)", name, backendNames())
|
|
}
|
|
return b, nil
|
|
}
|