Add Antigravity (agy) as a third agent backend for gemini-* models

This commit is contained in:
Ian Gulliver
2026-10-18 16:05:50 -07:00
parent a3680cd416
commit 543d1289e3
15 changed files with 1281 additions and 303 deletions
+215
View File
@@ -0,0 +1,215 @@
package agents
import (
"context"
"encoding/json"
"fmt"
"log/slog"
"strings"
"sync"
"g.fc.run/ai/byre/internal/antigravity"
)
type agySteps struct {
mu sync.Mutex
text map[int]*strings.Builder
tools map[string]bool
}
func newAgySteps() *agySteps {
return &agySteps{text: map[int]*strings.Builder{}, tools: map[string]bool{}}
}
func (m *Manager) handleAntigravity(ctx context.Context, l *live, p antigravityProcess, ev antigravity.Event) error {
switch ev.Kind {
case "init":
return nil
case "step_update":
su, err := ev.StepUpdate()
if err != nil {
return err
}
return m.agyStep(ctx, l, p, ev, su)
case "result":
res, err := ev.Result()
if err != nil {
return err
}
return m.agyTurnEnded(ctx, l, p, res)
}
raw := map[string]any{}
err := json.Unmarshal(ev.Raw, &raw)
if err != nil {
return err
}
return m.emit(ctx, l, agentActor, "agent.vendor", raw)
}
func (m *Manager) agyStep(ctx context.Context, l *live, p antigravityProcess, ev antigravity.Event, su antigravity.StepUpdate) error {
switch su.StepType {
case "user_input":
return nil
case "agent_response":
p.steps.mu.Lock()
b := p.steps.text[su.StepIndex]
if b == nil {
b = &strings.Builder{}
p.steps.text[su.StepIndex] = b
}
b.WriteString(su.TextDelta)
done := su.State != "ACTIVE"
if done {
delete(p.steps.text, su.StepIndex)
}
p.steps.mu.Unlock()
if su.TextDelta != "" {
m.store.Broadcast(m.event(l, agentActor, "agent.delta", map[string]any{"text": su.TextDelta}))
}
text := strings.TrimSpace(b.String())
if !done || text == "" {
return nil
}
m.mu.Lock()
l.lastText = text
m.mu.Unlock()
return m.emit(ctx, l, agentActor, "agent.message", map[string]any{"text": text})
case "tool":
id := fmt.Sprintf("%s/%d", su.ConversationID, su.StepIndex)
info := antigravity.ToolInfo{Name: su.ToolName}
if su.ToolInfo != nil {
info = *su.ToolInfo
}
p.steps.mu.Lock()
called := p.steps.tools[id]
p.steps.tools[id] = su.State == "ACTIVE"
if su.State != "ACTIVE" {
delete(p.steps.tools, id)
}
p.steps.mu.Unlock()
if !called {
name, input, err := agyToolCall(su.ToolName, info.Parameters)
if err != nil {
return err
}
err = m.emit(ctx, l, agentActor, "agent.tool_call", map[string]any{"id": id, "name": name, "input": input})
if err != nil {
return err
}
}
if su.State == "ACTIVE" {
return nil
}
content := info.Output
if info.Error != "" {
content = info.Error
}
return m.emit(ctx, l, agentActor, "agent.tool_result", map[string]any{"tool_use_id": id, "is_error": su.State != "DONE" || info.Error != "", "content": content})
}
raw := map[string]any{}
err := json.Unmarshal(ev.Raw, &raw)
if err != nil {
return err
}
return m.emit(ctx, l, agentActor, "agent.vendor", raw)
}
func agyToolCall(name string, params json.RawMessage) (string, any, error) {
switch name {
case "run_command":
p := struct {
CommandLine string
}{}
err := json.Unmarshal(params, &p)
return "Bash", map[string]any{"command": p.CommandLine}, err
case "call_mcp_tool":
p := struct {
ServerName string
ToolName string
Arguments json.RawMessage
}{}
err := json.Unmarshal(params, &p)
if err != nil {
return "", nil, err
}
args, err := decode(p.Arguments)
return "mcp__" + strings.TrimPrefix(p.ServerName, "byre_") + "__" + p.ToolName, args, err
}
input, err := decode(params)
return name, input, err
}
func (m *Manager) agyTurnEnded(ctx context.Context, l *live, p antigravityProcess, res antigravity.Result) error {
m.mu.Lock()
final := l.lastText
m.mu.Unlock()
success := res.Status == "SUCCESS"
bounce := false
if success && l.implementer() {
var err error
bounce, err = m.takeBounce(ctx, l, final)
if err != nil {
return err
}
}
if bounce {
err := m.emit(ctx, l, "byre", "user.message", map[string]any{"text": proseQuestionNudge, "mid_turn": true, "bounce": true})
if err != nil {
return err
}
m.mu.Lock()
l.lastText = ""
m.mu.Unlock()
go func() {
err := p.s.Send(proseQuestionNudge)
if err != nil {
slog.Error("send prose question nudge", "session", l.id, "err", err)
}
}()
return nil
}
r := endResult{
subtype: strings.ToLower(res.Status),
isError: !success,
result: final,
}
if !success {
r.result = res.Error
}
r.payload = map[string]any{
"subtype": r.subtype,
"is_error": r.isError,
"result": r.result,
}
return m.turnEnded(ctx, l, r)
}
func (m *Manager) agyBeforeModel(l *live) antigravity.HookFunc {
return func(ctx context.Context, input json.RawMessage) (map[string]any, error) {
in := struct {
InvocationNum int `json:"invocationNum"`
}{}
err := json.Unmarshal(input, &in)
if err != nil {
return nil, err
}
if in.InvocationNum == 0 || l.reviewID != "" {
return map[string]any{}, nil
}
mt := m.deliverMidTurn(ctx, l)
if mt.text == "" {
return map[string]any{}, nil
}
return map[string]any{"injectSteps": []map[string]any{{"userMessage": mt.text}}}, nil
}
}
+3
View File
@@ -25,6 +25,9 @@ func (m *Manager) Fork(ctx context.Context, src api.Task, project api.Project, n
if err != nil {
return api.Task{}, err
}
if srcSess.Vendor == api.Antigravity {
return api.Task{}, fmt.Errorf("%w: snapshot %d of task %s is from an Antigravity session, and agy can't fork a conversation", ErrConflict, n, src.Id)
}
events, err := m.store.TaskEvents(ctx, src.Id, 0, -1)
if err != nil {
return api.Task{}, err
+43 -39
View File
@@ -35,15 +35,16 @@ const agentActor = "agent"
const restartNotice = "byre restarted while you were mid-turn, so your last turn was cut off. Re-check the working tree (git status and the files you were editing) before continuing where you left off."
type Manager struct {
store *store.Store
worktreeRoot string
leadRoot string
handsRoot string
codexHome string
status func(context.Context) (api.Status, error)
addProject func(ctx context.Context, name, repoPath, createdBy string) (api.Project, error)
clients *clients.Registry
terminals *terminal.Terminals
store *store.Store
worktreeRoot string
leadRoot string
handsRoot string
codexHome string
antigravityRoot string
status func(context.Context) (api.Status, error)
addProject func(ctx context.Context, name, repoPath, createdBy string) (api.Project, error)
clients *clients.Registry
terminals *terminal.Terminals
mu sync.Mutex
live map[string]*live
@@ -125,37 +126,38 @@ func (l *live) implementer() bool {
return l.owner == "" && l.reviewID == ""
}
func New(st *store.Store, worktreeRoot, leadRoot, handsRoot, codexHome string, status func(context.Context) (api.Status, error), addProject func(ctx context.Context, name, repoPath, createdBy string) (api.Project, error), clientRegistry *clients.Registry, terminals *terminal.Terminals) *Manager {
func New(st *store.Store, worktreeRoot, leadRoot, handsRoot, codexHome, antigravityRoot string, status func(context.Context) (api.Status, error), addProject func(ctx context.Context, name, repoPath, createdBy string) (api.Project, error), clientRegistry *clients.Registry, terminals *terminal.Terminals) *Manager {
m := &Manager{
store: st,
worktreeRoot: worktreeRoot,
leadRoot: leadRoot,
handsRoot: handsRoot,
codexHome: codexHome,
status: status,
addProject: addProject,
clients: clientRegistry,
terminals: terminals,
live: map[string]*live{},
starting: map[string]*start{},
deliverLocks: map[string]*sessionLock{},
rateLimits: map[api.AgentSessionVendor]map[string]any{},
leadAnswered: map[string]bool{},
paneFocus: map[string]api.PaneFocus{},
paneReported: map[string]time.Time{},
paneMoved: map[string]time.Time{},
humanMoved: map[string]time.Time{},
moveRefused: map[string]bool{},
startedTodo: map[string]time.Time{},
startRefused: map[string]bool{},
finishing: map[string]bool{},
handoffModel: map[string]string{},
mergeTurns: map[string]mergeTurn{},
signals: make(chan []api.Event, 1024),
snaps: map[string]*snapState{},
merges: map[string]*projectMerge{},
bad: newBadLines(),
watchStop: make(chan struct{}),
store: st,
worktreeRoot: worktreeRoot,
leadRoot: leadRoot,
handsRoot: handsRoot,
codexHome: codexHome,
antigravityRoot: antigravityRoot,
status: status,
addProject: addProject,
clients: clientRegistry,
terminals: terminals,
live: map[string]*live{},
starting: map[string]*start{},
deliverLocks: map[string]*sessionLock{},
rateLimits: map[api.AgentSessionVendor]map[string]any{},
leadAnswered: map[string]bool{},
paneFocus: map[string]api.PaneFocus{},
paneReported: map[string]time.Time{},
paneMoved: map[string]time.Time{},
humanMoved: map[string]time.Time{},
moveRefused: map[string]bool{},
startedTodo: map[string]time.Time{},
startRefused: map[string]bool{},
finishing: map[string]bool{},
handoffModel: map[string]string{},
mergeTurns: map[string]mergeTurn{},
signals: make(chan []api.Event, 1024),
snaps: map[string]*snapState{},
merges: map[string]*projectMerge{},
bad: newBadLines(),
watchStop: make(chan struct{}),
}
go m.signalLoop()
go m.readCodexRateLimit()
@@ -814,6 +816,8 @@ func (m *Manager) startLive(ctx context.Context, sess api.AgentSession, task api
switch l.vendor {
case api.Codex:
l.sess, err = m.startCodex(sctx, sess, l, g, servers, stdio, env)
case api.Antigravity:
l.sess, err = m.startAntigravity(sctx, sess, l, g, servers, stdio, env)
default:
l.sess, err = m.startClaude(sctx, sess, l, g, servers, stdio, env)
}
+87 -1
View File
@@ -5,8 +5,11 @@ import (
"fmt"
"log/slog"
"os"
"path/filepath"
"strings"
"time"
"g.fc.run/ai/byre/internal/antigravity"
"g.fc.run/ai/byre/internal/api"
"g.fc.run/ai/byre/internal/claude"
"g.fc.run/ai/byre/internal/codex"
@@ -32,7 +35,7 @@ type agentProcess interface {
}
func initialVendorSessionID(vendor api.AgentSessionVendor, id string) string {
if vendor == api.Codex {
if vendor != api.Claude {
return ""
}
return id
@@ -238,3 +241,86 @@ func (p codexProcess) drive(m *Manager, l *live) {
}
}
}
type antigravityProcess struct {
s *antigravity.Session
steps *agySteps
}
func (m *Manager) startAntigravity(ctx context.Context, sess api.AgentSession, l *live, g string, servers []*mcp.Server, stdio []mcp.Stdio, env []string) (agentProcess, error) {
opts := antigravity.Options{
Dir: filepath.Join(m.antigravityRoot, sess.Id),
Cwd: l.dir,
Model: sess.Model,
Resume: sess.VendorSessionId,
GitName: l.gitName,
GitEmail: l.gitEmail,
Env: env,
Guidance: g,
Notes: strings.TrimSpace(l.notes),
Reminder: g,
MCPServers: servers,
StdioMCP: stdio,
Hooks: map[string]antigravity.HookFunc{"PreInvocation": m.agyBeforeModel(l)},
OnBadLine: func(line []byte, reason string) { m.recordBadLine(l, line, reason) },
}
s, err := antigravity.Start(ctx, opts)
if err != nil {
return nil, fmt.Errorf("start agy: %w", err)
}
if sess.VendorSessionId == "" {
err = m.store.SetVendorSessionID(ctx, sess.Id, s.ID())
if err != nil {
s.Kill()
return nil, err
}
}
return antigravityProcess{s: s, steps: newAgySteps()}, nil
}
func (p antigravityProcess) send(ctx context.Context, text string, images []image) error {
imgs := []antigravity.Image{}
for _, img := range images {
imgs = append(imgs, antigravity.Image{MediaType: img.mediaType, Data: img.data})
}
return p.s.Send(text, imgs...)
}
func (p antigravityProcess) interrupt(ctx context.Context) error {
return p.s.Interrupt()
}
func (p antigravityProcess) setModel(ctx context.Context, model string) error {
return p.s.SetModel(model)
}
func (p antigravityProcess) kill() { p.s.Kill() }
func (p antigravityProcess) pid() int { return p.s.Pid() }
func (p antigravityProcess) lastLine() time.Time { return p.s.LastLine() }
func (p antigravityProcess) exited() bool { return p.s.Exited() }
func (p antigravityProcess) discard() {
for range p.s.Events() {
}
}
func (p antigravityProcess) drive(m *Manager, l *live) {
ctx := context.Background()
for ev := range p.s.Events() {
start := time.Now()
if wait := start.Sub(ev.Read); wait > 200*time.Millisecond {
slog.Info("agent event waited", "session", l.id, "event", ev.Kind, "wait", wait)
}
err := m.handleAntigravity(ctx, l, p, ev)
if err != nil {
slog.Error("record agent event", "session", l.id, "event", ev.Kind, "err", err)
}
if isDecodeError(err) {
m.recordBadLine(l, ev.Raw, fmt.Sprintf("decode %s: %s", ev.Kind, err))
}
if took := time.Since(start); took > 200*time.Millisecond {
slog.Info("agent event slow", "session", l.id, "event", ev.Kind, "took", took)
}
}
}
+1 -1
View File
@@ -46,7 +46,7 @@ func (m *Manager) subtaskTools(l *live) []mcp.Tool {
`{"type": "object", "properties": {
"title": {"type": "string", "description": "Short task title, shown in the task list."},
"prompt": {"type": "string", "description": "The new agent's first message: a complete, self-contained brief."},
"model": {"type": "string", "description": "Model for the new agent: gpt-* models run on Codex, every other model on Claude Code. Defaults to this session's model."}
"model": {"type": "string", "description": "Model for the new agent: gpt-* models run on Codex, gemini-* models on Antigravity, every other model on Claude Code. Defaults to this session's model."}
}, "required": ["title", "prompt"]}`,
func(ctx context.Context, creator api.User, raw json.RawMessage) (string, error) {
args := struct {
+1 -1
View File
@@ -652,7 +652,7 @@ const workflowFileHelp = "A workflow's file is YAML: an optional description, th
"after (the names of the stages whose outputs it gets; it starts once all of them have finished; absent means the previous stage in the list, and for the first stage none; [] means it starts from the brief), " +
"base (parent, the default: branch from the commit its one after stage finished at, or for a stage with none, from where the workflow was started; main: branch from the default branch; " +
"refused on a stage with more than one after stage, which always branches from the default branch and is told the branches it can merge), " +
"model (which also picks the vendor: gpt-* models run on Codex, every other model on Claude Code) and prompt (the stage agent's instructions). " +
"model (which also picks the vendor: gpt-* models run on Codex, gemini-* models on Antigravity, every other model on Claude Code) and prompt (the stage agent's instructions). " +
"In place of model, agents (a list of at least two, each with name, model and prompt) runs the stage as a team: those agents work in the stage's one task and worktree and talk through a shared board; " +
"each gets the stage's prompt and then its own; the first agent finishes the stage. " +
"Every stage runs as its own task. Exactly one stage is final (no other stage comes after it), and cycles are refused. " +
+581
View File
@@ -0,0 +1,581 @@
package antigravity
import (
"bufio"
"bytes"
"context"
"crypto/rand"
"encoding/json"
"errors"
"fmt"
"io"
"log/slog"
"net"
"net/http"
"os"
"os/exec"
"path/filepath"
"strings"
"sync"
"sync/atomic"
"syscall"
"time"
"g.fc.run/ai/byre/internal/mcp"
)
const maxRuleBytes = 24000
type HookFunc func(ctx context.Context, input json.RawMessage) (map[string]any, error)
type Options struct {
Dir string
Cwd string
Model string
Resume string
GitName string
GitEmail string
Env []string
Guidance string
Notes string
Reminder string
MCPServers []*mcp.Server
StdioMCP []mcp.Stdio
Hooks map[string]HookFunc
OnBadLine func(line []byte, reason string)
}
type Image struct {
MediaType string
Data []byte
}
type Event struct {
Kind string
Raw json.RawMessage
Read time.Time
}
type Usage struct {
InputTokens int `json:"input_tokens"`
OutputTokens int `json:"output_tokens"`
ThinkingTokens int `json:"thinking_tokens"`
CacheReadTokens int `json:"cache_read_tokens"`
TotalTokens int `json:"total_tokens"`
}
type ToolInfo struct {
Name string `json:"name"`
Parameters json.RawMessage `json:"parameters"`
Output string `json:"output"`
Error string `json:"error"`
}
type StepUpdate struct {
ConversationID string `json:"conversation_id"`
StepIndex int `json:"step_index"`
State string `json:"state"`
StepType string `json:"step_type"`
TextDelta string `json:"text_delta"`
ToolName string `json:"tool_name"`
ToolInfo *ToolInfo `json:"tool_info"`
DurationSeconds float64 `json:"duration_seconds"`
Usage *Usage `json:"usage"`
}
type Result struct {
ConversationID string `json:"conversation_id"`
Status string `json:"status"`
Response string `json:"response"`
Error string `json:"error"`
DurationSeconds float64 `json:"duration_seconds"`
NumTurns int `json:"num_turns"`
Usage Usage `json:"usage"`
}
var knownEvents = map[string]bool{"init": true, "step_update": true, "result": true}
func (e Event) payload(v any) error {
fields := map[string]json.RawMessage{}
err := json.Unmarshal(e.Raw, &fields)
if err != nil {
return err
}
return json.Unmarshal(fields[e.Kind], v)
}
func (e Event) StepUpdate() (StepUpdate, error) {
su := StepUpdate{}
return su, e.payload(&su)
}
func (e Event) Result() (Result, error) {
res := Result{}
return res, e.payload(&res)
}
type proc struct {
cmd *exec.Cmd
stdin io.WriteCloser
done chan struct{}
expected bool
}
type Session struct {
opts Options
mu sync.Mutex
p *proc
model string
killed bool
images int
idMu sync.Mutex
convID string
events chan Event
dead chan struct{}
dieOnce sync.Once
started chan struct{}
initOnce sync.Once
srv *http.Server
base string
servers map[string]*mcp.Server
lastLine atomic.Int64
}
func Start(ctx context.Context, opts Options) (*Session, error) {
if len(opts.Guidance) > maxRuleBytes || len(opts.Notes) > maxRuleBytes {
return nil, fmt.Errorf("guidance (%d bytes) and notes (%d bytes) must each fit agy's %d-byte rule limit", len(opts.Guidance), len(opts.Notes), maxRuleBytes)
}
ln, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
return nil, err
}
token := rand.Text()
s := &Session{
opts: opts,
model: opts.Model,
convID: opts.Resume,
events: make(chan Event, 256),
dead: make(chan struct{}),
started: make(chan struct{}),
base: "http://" + ln.Addr().String() + "/" + token,
servers: map[string]*mcp.Server{},
}
for _, srv := range opts.MCPServers {
s.servers[srv.Name] = srv
}
s.lastLine.Store(time.Now().UnixNano())
mux := http.NewServeMux()
mux.HandleFunc("POST /"+token+"/mcp/{server}", s.serveMCP)
mux.HandleFunc("POST /"+token+"/hook/{event}", s.serveHook)
s.srv = &http.Server{Handler: mux}
go s.srv.Serve(ln)
err = s.writePlugin()
if err != nil {
s.die()
return nil, err
}
s.mu.Lock()
err = s.spawnLocked()
s.mu.Unlock()
if err != nil {
s.die()
return nil, err
}
select {
case <-s.started:
return s, nil
case <-s.dead:
return nil, errors.New("agy exited before it started a conversation")
case <-ctx.Done():
s.Kill()
return nil, ctx.Err()
}
}
func (s *Session) pluginDir() string {
return filepath.Join(s.opts.Dir, ".agents", "plugins", "byre")
}
func (s *Session) writePlugin() error {
dir := s.pluginDir()
err := os.MkdirAll(filepath.Join(dir, "rules"), 0o755)
if err != nil {
return err
}
servers := map[string]any{}
for _, srv := range s.opts.MCPServers {
servers[srv.Name] = map[string]any{"url": s.base + "/mcp/" + srv.Name}
}
for _, srv := range s.opts.StdioMCP {
servers[srv.Name] = map[string]any{"command": srv.Command, "args": srv.Args}
}
hooks := map[string]any{}
for event := range s.opts.Hooks {
hooks[event] = []map[string]any{{
"type": "command",
"command": fmt.Sprintf("curl -sS --max-time 25 --data-binary @- %s/hook/%s", s.base, event),
"timeout": 30,
}}
}
files := map[string]any{
"plugin.json": map[string]any{"name": "byre"},
"mcp_config.json": map[string]any{"mcpServers": servers},
"hooks.json": map[string]any{"byre": hooks},
}
for name, v := range files {
b, err := json.MarshalIndent(v, "", " ")
if err != nil {
return err
}
err = os.WriteFile(filepath.Join(dir, name), b, 0o644)
if err != nil {
return err
}
}
err = os.WriteFile(filepath.Join(dir, "rules", "AGENTS.md"), []byte(s.opts.Guidance), 0o644)
if err != nil {
return err
}
if s.opts.Notes == "" {
return nil
}
return os.WriteFile(filepath.Join(dir, "rules", "notes.md"), []byte("---\ntrigger: always_on\n---\n"+s.opts.Notes), 0o644)
}
func (s *Session) serveMCP(w http.ResponseWriter, r *http.Request) {
srv := s.servers[r.PathValue("server")]
if srv == nil {
http.NotFound(w, r)
return
}
body, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
msg := struct {
ID json.RawMessage `json:"id"`
}{}
err = json.Unmarshal(body, &msg)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
if len(msg.ID) == 0 {
w.WriteHeader(http.StatusAccepted)
return
}
resp, err := srv.Handle(r.Context(), body)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
w.Header().Set("Content-Type", "application/json")
w.Write(resp)
}
func (s *Session) serveHook(w http.ResponseWriter, r *http.Request) {
event := r.PathValue("event")
fn := s.opts.Hooks[event]
if fn == nil {
http.NotFound(w, r)
return
}
body, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
out, err := fn(r.Context(), body)
if err != nil {
slog.Error("agy hook failed", "event", event, "err", err)
out = map[string]any{}
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(out)
}
func (s *Session) spawnLocked() error {
args := []string{
"--input-format", "stream-json",
"--output-format", "stream-json",
"--dangerously-skip-permissions",
"--add-dir", s.opts.Dir,
}
if s.model != "" {
args = append(args, "--model", s.model)
}
if id := s.ID(); id != "" {
args = append(args, "--conversation", id)
}
args = append(args, "--print", "")
cmd := exec.Command("agy", args...)
cmd.Dir = s.opts.Cwd
cmd.Stderr = os.Stderr
cmd.Env = append(os.Environ(),
"GIT_AUTHOR_NAME="+s.opts.GitName,
"GIT_AUTHOR_EMAIL="+s.opts.GitEmail,
"GIT_COMMITTER_NAME="+s.opts.GitName,
"GIT_COMMITTER_EMAIL="+s.opts.GitEmail,
)
cmd.Env = append(cmd.Env, s.opts.Env...)
stdin, err := cmd.StdinPipe()
if err != nil {
return err
}
stdout, err := cmd.StdoutPipe()
if err != nil {
return err
}
err = cmd.Start()
if err != nil {
return fmt.Errorf("start agy: %w", err)
}
p := &proc{cmd: cmd, stdin: stdin, done: make(chan struct{})}
s.p = p
go s.readLoop(p, stdout)
return nil
}
func (s *Session) readLoop(p *proc, stdout io.Reader) {
r := bufio.NewReader(stdout)
for {
line, err := r.ReadBytes('\n')
line = bytes.TrimRight(line, "\r\n")
if len(line) > 0 {
s.lastLine.Store(time.Now().UnixNano())
s.handleLine(line)
}
if err != nil {
if err != io.EOF {
slog.Error("read agy stdout", "err", err)
}
break
}
}
p.cmd.Wait()
close(p.done)
s.mu.Lock()
expected := p.expected && !s.killed
s.mu.Unlock()
if !expected {
s.die()
}
}
func (s *Session) handleLine(line []byte) {
read := time.Now()
if line[0] != '{' {
s.badLine(line, "not a JSON object")
return
}
env := struct {
Event string `json:"event"`
ConversationID string `json:"conversation_id"`
}{}
err := json.Unmarshal(line, &env)
if err != nil {
s.badLine(line, err.Error())
return
}
if !knownEvents[env.Event] {
s.badLine(line, fmt.Sprintf("unrecognised event %q", env.Event))
}
if env.Event == "init" {
s.idMu.Lock()
if s.convID == "" {
s.convID = env.ConversationID
}
s.idMu.Unlock()
s.initOnce.Do(func() { close(s.started) })
}
s.events <- Event{Kind: env.Event, Raw: append(json.RawMessage{}, line...), Read: read}
}
func (s *Session) die() {
s.dieOnce.Do(func() {
close(s.events)
close(s.dead)
s.srv.Close()
err := os.RemoveAll(s.opts.Dir)
if err != nil {
slog.Error("remove agy session dir", "dir", s.opts.Dir, "err", err)
}
})
}
func (s *Session) restartLocked() error {
p := s.p
select {
case <-p.done:
case <-time.After(10 * time.Second):
p.cmd.Process.Kill()
<-p.done
}
return s.spawnLocked()
}
func (s *Session) Events() <-chan Event {
return s.events
}
func (s *Session) ID() string {
s.idMu.Lock()
defer s.idMu.Unlock()
return s.convID
}
func (s *Session) Pid() int {
s.mu.Lock()
defer s.mu.Unlock()
return s.p.cmd.Process.Pid
}
func (s *Session) LastLine() time.Time {
return time.Unix(0, s.lastLine.Load())
}
func (s *Session) Exited() bool {
select {
case <-s.dead:
return true
default:
return false
}
}
type block struct {
Type string `json:"type"`
Text string `json:"text"`
}
func (s *Session) Send(text string, images ...Image) error {
s.mu.Lock()
defer s.mu.Unlock()
if s.killed {
return errors.New("agy session was killed")
}
if s.p.expected {
err := s.restartLocked()
if err != nil {
s.die()
return err
}
}
content := []block{}
if s.opts.Reminder != "" {
content = append(content, block{Type: "text", Text: "<system-reminder>\n" + s.opts.Reminder + "\n</system-reminder>"})
}
for _, img := range images {
s.images++
path := filepath.Join(s.opts.Dir, "images", fmt.Sprintf("%d.%s", s.images, strings.TrimPrefix(img.MediaType, "image/")))
err := os.MkdirAll(filepath.Dir(path), 0o755)
if err != nil {
return err
}
err = os.WriteFile(path, img.Data, 0o644)
if err != nil {
return err
}
content = append(content, block{Type: "text", Text: "The human attached an image, saved at " + path + "; view it with view_file."})
}
content = append(content, block{Type: "text", Text: text})
b, err := json.Marshal(map[string]any{
"event": "user",
"message": map[string]any{"role": "user", "content": content},
})
if err != nil {
return err
}
_, err = s.p.stdin.Write(append(b, '\n'))
return err
}
func (s *Session) Interrupt() error {
s.mu.Lock()
p := s.p
p.expected = true
err := p.cmd.Process.Signal(syscall.SIGINT)
s.mu.Unlock()
if err != nil {
return err
}
go func() {
<-p.done
s.mu.Lock()
defer s.mu.Unlock()
if s.killed {
s.die()
return
}
if s.p != p {
return
}
err := s.spawnLocked()
if err != nil {
slog.Error("restart agy after interrupt", "err", err)
s.die()
}
}()
return nil
}
func (s *Session) SetModel(model string) error {
s.mu.Lock()
defer s.mu.Unlock()
if model == s.model {
return nil
}
s.model = model
s.p.expected = true
s.p.stdin.Close()
err := s.restartLocked()
if err != nil {
s.die()
}
return err
}
func (s *Session) Kill() {
s.mu.Lock()
s.killed = true
p := s.p
s.mu.Unlock()
if p != nil {
p.cmd.Process.Kill()
<-p.done
}
s.die()
}
func (s *Session) badLine(line []byte, reason string) {
slog.Warn("bad agy line", "reason", reason, "line", string(line))
if s.opts.OnBadLine != nil {
s.opts.OnBadLine(line, reason)
}
}
+241 -238
View File
@@ -86,13 +86,16 @@ func (e AgentSessionState) Valid() bool {
// Defines values for AgentSessionVendor.
const (
Claude AgentSessionVendor = "claude"
Codex AgentSessionVendor = "codex"
Antigravity AgentSessionVendor = "antigravity"
Claude AgentSessionVendor = "claude"
Codex AgentSessionVendor = "codex"
)
// Valid indicates whether the value is a known member of the AgentSessionVendor enum.
func (e AgentSessionVendor) Valid() bool {
switch e {
case Antigravity:
return true
case Claude:
return true
case Codex:
@@ -596,10 +599,10 @@ type AgentSession struct {
// TaskId Empty for a lead session.
TaskId string `json:"task_id"`
// Vendor Follows from the model the session was created with: gpt-* models run on Codex, every other model on Claude Code.
// Vendor Follows from the model the session was created with: gpt-* models run on Codex, gemini-* models on Antigravity, every other model on Claude Code.
Vendor AgentSessionVendor `json:"vendor"`
// VendorSessionId Claude Code's session id, chosen by byre; or the Codex thread id, empty until the session's first turn starts its thread.
// VendorSessionId Claude Code's session id, chosen by byre; or the Codex thread id or Antigravity conversation id, empty until the session first starts.
VendorSessionId string `json:"vendor_session_id"`
}
@@ -609,7 +612,7 @@ type AgentSessionRole string
// AgentSessionState defines model for AgentSession.State.
type AgentSessionState string
// AgentSessionVendor Follows from the model the session was created with: gpt-* models run on Codex, every other model on Claude Code.
// AgentSessionVendor Follows from the model the session was created with: gpt-* models run on Codex, gemini-* models on Antigravity, every other model on Claude Code.
type AgentSessionVendor string
// Attachment defines model for Attachment.
@@ -1090,7 +1093,7 @@ type Stage struct {
// Base Where the stage's task branches from. parent (the default): the commit its one after stage finished at, or for a stage with none, where the workflow was started from. main: the project's default branch. Refused on a stage with more than one after stage, which always branches from the default branch.
Base *StageBase `json:"base,omitempty"`
// Model The stage agent's model, which also picks its vendor: gpt-* models run on Codex, every other model on Claude Code. Required without agents, refused with them.
// Model The stage agent's model, which also picks its vendor: gpt-* models run on Codex, gemini-* models on Antigravity, every other model on Claude Code. Required without agents, refused with them.
Model string `json:"model,omitempty"`
// Name One line of 1 to 100 characters, unique within the workflow.
@@ -3798,238 +3801,238 @@ var swaggerSpec = []string{
"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",
"ewA=",
"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",
"YssYaXgUony5VOY7NGNvqefefX6vIk0fP/7vAQA=",
}
// decodeSpec returns the embedded OpenAPI spec as raw JSON bytes,
+1 -1
View File
@@ -69,7 +69,7 @@ func New(dataDir string, portFirst int) (*Server, error) {
}
}
s.agents = agents.New(s.store, filepath.Join(dataDir, "worktrees"), filepath.Join(dataDir, "leads"), filepath.Join(dataDir, "hands"), filepath.Join(dataDir, "codex"), s.status, s.createProject, s.clients, s.terminals)
s.agents = agents.New(s.store, filepath.Join(dataDir, "worktrees"), filepath.Join(dataDir, "leads"), filepath.Join(dataDir, "hands"), filepath.Join(dataDir, "codex"), filepath.Join(dataDir, "antigravity"), s.status, s.createProject, s.clients, s.terminals)
err = s.agents.Recover(context.Background())
if err != nil {
s.store.Close()
+4 -1
View File
@@ -15,8 +15,11 @@ var ErrInvalid = errors.New("invalid workflow")
const maxNameRunes = 100
func VendorOf(model string) api.AgentSessionVendor {
if strings.HasPrefix(model, "gpt-") {
switch {
case strings.HasPrefix(model, "gpt-"):
return api.Codex
case strings.HasPrefix(model, "gemini-"):
return api.Antigravity
}
return api.Claude
}