Organize Agentbox runtime code
Co-authored-by: codegirl007 <codegirl-007@users.noreply.github.com>
This commit is contained in:
co-authored by
codegirl007
parent
f2fee5d26b
commit
4d2fb4a733
@@ -0,0 +1,64 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
|
||||
"agentbox/internal/trace"
|
||||
)
|
||||
|
||||
func listRuns() error {
|
||||
projectRoot, err := findProjectRoot()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
runs, err := trace.List(projectRoot)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(runs) == 0 {
|
||||
fmt.Println("No recorded runs.")
|
||||
return nil
|
||||
}
|
||||
for _, run := range runs {
|
||||
fmt.Printf("%s %-8s %-20s %s\n",
|
||||
run.ID, run.Status, run.Agent, run.Task)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func inspect(args []string) error {
|
||||
if len(args) != 1 {
|
||||
return errors.New("inspect requires one run ID")
|
||||
}
|
||||
projectRoot, err := findProjectRoot()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
runID := args[0]
|
||||
run, err := trace.Read(projectRoot, runID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
steps, err := trace.ReadSteps(projectRoot, runID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
output := struct {
|
||||
Run trace.Run `json:"run"`
|
||||
Steps []trace.StepRecord `json:"steps"`
|
||||
Directory string `json:"directory"`
|
||||
}{
|
||||
Run: run,
|
||||
Steps: steps,
|
||||
Directory: filepath.Join(projectRoot, ".agentbox", "runs", run.ID),
|
||||
}
|
||||
data, err := json.MarshalIndent(output, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println(string(data))
|
||||
return nil
|
||||
}
|
||||
@@ -1,24 +1,13 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"agentbox/internal/agent"
|
||||
"agentbox/internal/appspec"
|
||||
"agentbox/internal/environment/dockerx11"
|
||||
"agentbox/internal/phase1"
|
||||
agentRuntime "agentbox/internal/runtime"
|
||||
"agentbox/internal/trace"
|
||||
)
|
||||
|
||||
func main() {
|
||||
@@ -50,188 +39,6 @@ func main() {
|
||||
}
|
||||
}
|
||||
|
||||
type runOptions struct {
|
||||
path string
|
||||
task string
|
||||
agent string
|
||||
model string
|
||||
maxSteps int
|
||||
}
|
||||
|
||||
func run(ctx context.Context, args []string) error {
|
||||
options, err := parseRunOptions(args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
root, err := findRoot()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
command, err := appspec.Resolve(options.path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
controller, err := selectAgent(options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
store, err := trace.New(root, options.task, options.path, controller.Name())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
env := dockerx11.New(dockerx11.Config{
|
||||
ProjectRoot: root,
|
||||
RunID: store.ID(),
|
||||
Output: os.Stdout,
|
||||
})
|
||||
err = agentRuntime.Run(ctx, agentRuntime.Config{
|
||||
Task: options.task,
|
||||
Command: command,
|
||||
Agent: controller,
|
||||
Env: env,
|
||||
Trace: store,
|
||||
MaxSteps: options.maxSteps,
|
||||
Output: os.Stdout,
|
||||
})
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Run artifacts: %s\n", store.Directory())
|
||||
return err
|
||||
}
|
||||
fmt.Printf("Replay written to:\n%s\n", store.Directory())
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseRunOptions(args []string) (runOptions, error) {
|
||||
options := runOptions{agent: "deterministic", model: "gpt-5", maxSteps: 20}
|
||||
for index := 0; index < len(args); index++ {
|
||||
arg := args[index]
|
||||
var name, value string
|
||||
if strings.HasPrefix(arg, "--") {
|
||||
name, value, _ = strings.Cut(strings.TrimPrefix(arg, "--"), "=")
|
||||
if value == "" {
|
||||
index++
|
||||
if index >= len(args) {
|
||||
return options, fmt.Errorf("--%s requires a value", name)
|
||||
}
|
||||
value = args[index]
|
||||
}
|
||||
switch name {
|
||||
case "task":
|
||||
options.task = value
|
||||
case "agent":
|
||||
options.agent = value
|
||||
case "model":
|
||||
options.model = value
|
||||
case "max-steps":
|
||||
number, err := strconv.Atoi(value)
|
||||
if err != nil || number < 1 {
|
||||
return options, errors.New("--max-steps must be a positive integer")
|
||||
}
|
||||
options.maxSteps = number
|
||||
default:
|
||||
return options, fmt.Errorf("unknown flag --%s", name)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if options.path != "" {
|
||||
return options, errors.New("run accepts exactly one application path")
|
||||
}
|
||||
options.path = arg
|
||||
}
|
||||
if options.path == "" {
|
||||
return options, errors.New("run requires an application path")
|
||||
}
|
||||
if options.task == "" {
|
||||
return options, errors.New("run requires --task")
|
||||
}
|
||||
return options, nil
|
||||
}
|
||||
|
||||
func selectAgent(options runOptions) (agent.Agent, error) {
|
||||
switch options.agent {
|
||||
case "deterministic":
|
||||
return &agent.Deterministic{}, nil
|
||||
case "openai":
|
||||
return agent.NewOpenAI(agent.OpenAIConfig{
|
||||
APIKey: os.Getenv("OPENAI_API_KEY"),
|
||||
Model: options.model,
|
||||
})
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown agent %q (want deterministic or openai)", options.agent)
|
||||
}
|
||||
}
|
||||
|
||||
func listRuns() error {
|
||||
root, err := findRoot()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
runs, err := trace.List(root)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(runs) == 0 {
|
||||
fmt.Println("No recorded runs.")
|
||||
return nil
|
||||
}
|
||||
for _, run := range runs {
|
||||
fmt.Printf("%s %-8s %-20s %s\n",
|
||||
run.ID, run.Status, run.Agent, run.Task)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func inspect(args []string) error {
|
||||
if len(args) != 1 {
|
||||
return errors.New("inspect requires one run ID")
|
||||
}
|
||||
root, err := findRoot()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
run, err := trace.Read(root, args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
steps, err := trace.ReadSteps(root, args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
output := struct {
|
||||
Run trace.Run `json:"run"`
|
||||
Steps []trace.StepRecord `json:"steps"`
|
||||
Directory string `json:"directory"`
|
||||
}{
|
||||
Run: run,
|
||||
Steps: steps,
|
||||
Directory: filepath.Join(root, ".agentbox", "runs", run.ID),
|
||||
}
|
||||
data, err := json.MarshalIndent(output, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println(string(data))
|
||||
return nil
|
||||
}
|
||||
|
||||
func findRoot() (string, error) {
|
||||
current, err := os.Getwd()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
for {
|
||||
data, readErr := os.ReadFile(filepath.Join(current, "go.mod"))
|
||||
if readErr == nil && bytes.Contains(data, []byte("module agentbox")) {
|
||||
return current, nil
|
||||
}
|
||||
parent := filepath.Dir(current)
|
||||
if parent == current {
|
||||
return "", errors.New("run agentbox from inside its module directory")
|
||||
}
|
||||
current = parent
|
||||
}
|
||||
}
|
||||
|
||||
func usage() {
|
||||
fmt.Fprintln(os.Stderr, `usage:
|
||||
agentbox run <path> --task "<task>" [--agent deterministic|openai] [--model <model>]
|
||||
|
||||
@@ -9,7 +9,9 @@ func TestParseRunOptionsAllowsFlagsAfterPath(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if options.path != "./game" || options.task != "move right" || options.maxSteps != 7 {
|
||||
if options.applicationPath != "./game" ||
|
||||
options.task != "move right" ||
|
||||
options.maxSteps != 7 {
|
||||
t.Fatalf("options = %#v", options)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// findProjectRoot lets commands work from any directory inside the Agentbox
|
||||
// module. The module declaration is a stronger marker than a directory name.
|
||||
func findProjectRoot() (string, error) {
|
||||
currentDirectory, err := os.Getwd()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
for {
|
||||
goModule, readErr := os.ReadFile(filepath.Join(currentDirectory, "go.mod"))
|
||||
if readErr == nil && bytes.Contains(goModule, []byte("module agentbox")) {
|
||||
return currentDirectory, nil
|
||||
}
|
||||
parentDirectory := filepath.Dir(currentDirectory)
|
||||
if parentDirectory == currentDirectory {
|
||||
return "", errors.New("run agentbox from inside its module directory")
|
||||
}
|
||||
currentDirectory = parentDirectory
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"agentbox/internal/agent"
|
||||
"agentbox/internal/appspec"
|
||||
"agentbox/internal/environment/dockerx11"
|
||||
agentRuntime "agentbox/internal/runtime"
|
||||
"agentbox/internal/trace"
|
||||
)
|
||||
|
||||
type runOptions struct {
|
||||
applicationPath string
|
||||
task string
|
||||
agentName string
|
||||
modelName string
|
||||
maxSteps int
|
||||
}
|
||||
|
||||
func run(ctx context.Context, args []string) error {
|
||||
options, err := parseRunOptions(args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
projectRoot, err := findProjectRoot()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
applicationCommand, err := appspec.Resolve(options.applicationPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
controller, err := newAgent(options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
traceStore, err := trace.New(
|
||||
projectRoot,
|
||||
options.task,
|
||||
options.applicationPath,
|
||||
controller.Name(),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
desktopEnvironment := dockerx11.New(dockerx11.Config{
|
||||
ProjectRoot: projectRoot,
|
||||
RunID: traceStore.ID(),
|
||||
Output: os.Stdout,
|
||||
})
|
||||
err = agentRuntime.Run(ctx, agentRuntime.Config{
|
||||
Task: options.task,
|
||||
Command: applicationCommand,
|
||||
Controller: controller,
|
||||
Environment: desktopEnvironment,
|
||||
TraceStore: traceStore,
|
||||
MaxSteps: options.maxSteps,
|
||||
Output: os.Stdout,
|
||||
})
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Run artifacts: %s\n", traceStore.Directory())
|
||||
return err
|
||||
}
|
||||
fmt.Printf("Replay written to:\n%s\n", traceStore.Directory())
|
||||
return nil
|
||||
}
|
||||
|
||||
// parseRunOptions accepts flags before or after the application path so the CLI
|
||||
// matches the natural "agentbox run ./app --task ..." form shown in the docs.
|
||||
func parseRunOptions(args []string) (runOptions, error) {
|
||||
options := runOptions{
|
||||
agentName: "deterministic",
|
||||
modelName: "gpt-5",
|
||||
maxSteps: 20,
|
||||
}
|
||||
for index := 0; index < len(args); index++ {
|
||||
argument := args[index]
|
||||
if !strings.HasPrefix(argument, "--") {
|
||||
if options.applicationPath != "" {
|
||||
return options, errors.New("run accepts exactly one application path")
|
||||
}
|
||||
options.applicationPath = argument
|
||||
continue
|
||||
}
|
||||
|
||||
flagName, flagValue, _ := strings.Cut(strings.TrimPrefix(argument, "--"), "=")
|
||||
if flagValue == "" {
|
||||
index++
|
||||
if index >= len(args) {
|
||||
return options, fmt.Errorf("--%s requires a value", flagName)
|
||||
}
|
||||
flagValue = args[index]
|
||||
}
|
||||
switch flagName {
|
||||
case "task":
|
||||
options.task = flagValue
|
||||
case "agent":
|
||||
options.agentName = flagValue
|
||||
case "model":
|
||||
options.modelName = flagValue
|
||||
case "max-steps":
|
||||
maxSteps, err := strconv.Atoi(flagValue)
|
||||
if err != nil || maxSteps < 1 {
|
||||
return options, errors.New("--max-steps must be a positive integer")
|
||||
}
|
||||
options.maxSteps = maxSteps
|
||||
default:
|
||||
return options, fmt.Errorf("unknown flag --%s", flagName)
|
||||
}
|
||||
}
|
||||
if options.applicationPath == "" {
|
||||
return options, errors.New("run requires an application path")
|
||||
}
|
||||
if options.task == "" {
|
||||
return options, errors.New("run requires --task")
|
||||
}
|
||||
return options, nil
|
||||
}
|
||||
|
||||
func newAgent(options runOptions) (agent.Agent, error) {
|
||||
switch options.agentName {
|
||||
case "deterministic":
|
||||
return &agent.Deterministic{}, nil
|
||||
case "openai":
|
||||
return agent.NewOpenAI(agent.OpenAIConfig{
|
||||
APIKey: os.Getenv("OPENAI_API_KEY"),
|
||||
Model: options.modelName,
|
||||
})
|
||||
default:
|
||||
return nil, fmt.Errorf(
|
||||
"unknown agent %q (want deterministic or openai)",
|
||||
options.agentName,
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user