Skip to content
40 changes: 20 additions & 20 deletions cmd/attach/attach.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import (
"time"

tea "github.com/charmbracelet/bubbletea"
"github.com/coder/agentapi/lib/httpapi"
"github.com/coder/agentapi/lib/types"
"github.com/spf13/cobra"
sse "github.com/tmaxmax/go-sse"
"golang.org/x/term"
Expand All @@ -35,27 +35,27 @@ func (c *ChannelWriter) Receive() ([]byte, bool) {
}

type model struct {
screen string
conversation string
}

func (m model) Init() tea.Cmd {
// Just return `nil`, which means "no I/O right now, please."
return nil
}

type screenMsg struct {
screen string
type conversationMsg struct {
conversation string
}

type finishMsg struct{}

//lint:ignore U1000 The Update function is used by the Bubble Tea framework
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case screenMsg:
m.screen = msg.screen
if m.screen != "" && m.screen[len(m.screen)-1] != '\n' {
m.screen += "\n"
case conversationMsg:
m.conversation = msg.conversation
if m.conversation != "" && m.conversation[len(m.conversation)-1] != '\n' {
m.conversation += "\n"
}
case tea.KeyMsg:
if msg.String() == "ctrl+c" {
Expand All @@ -69,10 +69,10 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
}

func (m model) View() string {
return m.screen
return m.conversation
}

func ReadScreenOverHTTP(ctx context.Context, url string, ch chan<- httpapi.ScreenUpdateBody) error {
func ReadScreenOverHTTP(ctx context.Context, url string, ch chan<- types.ScreenUpdateBody) error {
req, _ := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
req.Header.Set("Content-Type", "application/json")

Expand All @@ -85,25 +85,25 @@ func ReadScreenOverHTTP(ctx context.Context, url string, ch chan<- httpapi.Scree
}()

for ev, err := range sse.Read(res.Body, &sse.ReadConfig{
// 256KB: screen can be big. The default terminal size is 80x1000,
// 256KB: conversation can be big. The default terminal size is 80x1000,
// which can be over 80000 bytes.
MaxEventSize: 256 * 1024,
}) {
if err != nil {
return xerrors.Errorf("failed to read sse: %w", err)
}
var screen httpapi.ScreenUpdateBody
var screen types.ScreenUpdateBody
if err := json.Unmarshal([]byte(ev.Data), &screen); err != nil {
return xerrors.Errorf("failed to unmarshal screen: %w", err)
return xerrors.Errorf("failed to unmarshal conversation: %w", err)
}
ch <- screen
}
return nil
}

func WriteRawInputOverHTTP(ctx context.Context, url string, msg string) error {
messageRequest := httpapi.MessageRequestBody{
Type: httpapi.MessageTypeRaw,
messageRequest := types.MessageRequestBody{
Type: types.MessageTypeRaw,
Content: msg,
}
messageRequestBytes, err := json.Marshal(messageRequest)
Expand Down Expand Up @@ -145,16 +145,16 @@ func runAttach(remoteUrl string) error {
}
tee := io.TeeReader(os.Stdin, stdinWriter)
p := tea.NewProgram(model{}, tea.WithInput(tee), tea.WithAltScreen())
screenCh := make(chan httpapi.ScreenUpdateBody, 64)
screenCh := make(chan types.ScreenUpdateBody, 64)

readScreenErrCh := make(chan error, 1)
go func() {
defer close(readScreenErrCh)
if err := ReadScreenOverHTTP(ctx, remoteUrl+"/internal/screen", screenCh); err != nil {
if err := ReadScreenOverHTTP(ctx, remoteUrl+"/internal/conversation", screenCh); err != nil {
if errors.Is(err, context.Canceled) {
return
}
readScreenErrCh <- xerrors.Errorf("failed to read screen: %w", err)
readScreenErrCh <- xerrors.Errorf("failed to read conversation: %w", err)
}
}()
writeRawInputErrCh := make(chan error, 1)
Expand Down Expand Up @@ -189,8 +189,8 @@ func runAttach(remoteUrl string) error {
if !ok {
return
}
p.Send(screenMsg{
screen: screenUpdate.Screen,
p.Send(conversationMsg{
conversation: screenUpdate.Screen,
})
}
}
Expand Down
49 changes: 31 additions & 18 deletions cmd/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,15 @@ import (
"sort"
"strings"

"github.com/coder/agentapi/lib/cli/msgfmt"
"github.com/coder/agentapi/lib/cli/termexec"
"github.com/coder/agentapi/lib/types"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"golang.org/x/xerrors"

"github.com/coder/agentapi/lib/httpapi"
"github.com/coder/agentapi/lib/logctx"
"github.com/coder/agentapi/lib/msgfmt"
"github.com/coder/agentapi/lib/termexec"
)

type AgentType = msgfmt.AgentType
Expand Down Expand Up @@ -68,14 +69,25 @@ func parseAgentType(firstArg string, agentTypeVar string) (AgentType, error) {
return AgentTypeCustom, nil
}

func parseInteractionType(interactionModeVar string) (types.InteractionType, error) {
return types.InteractionTypeCLI, nil
}

func runServer(ctx context.Context, logger *slog.Logger, argsToPass []string) error {
agent := argsToPass[0]
agentTypeValue := viper.GetString(FlagType)
interactionTypeValue := viper.GetString(FlagInteractionType)

agentType, err := parseAgentType(agent, agentTypeValue)
if err != nil {
return xerrors.Errorf("failed to parse agent type: %w", err)
}

interactionType, err := parseInteractionType(interactionTypeValue)
if err != nil {
return xerrors.Errorf("failed to parse interaction type: %w", err)
}

termWidth := viper.GetUint16(FlagTermWidth)
termHeight := viper.GetUint16(FlagTermHeight)

Expand Down Expand Up @@ -104,12 +116,13 @@ func runServer(ctx context.Context, logger *slog.Logger, argsToPass []string) er
}
port := viper.GetInt(FlagPort)
srv, err := httpapi.NewServer(ctx, httpapi.ServerConfig{
AgentType: agentType,
Process: process,
Port: port,
ChatBasePath: viper.GetString(FlagChatBasePath),
AllowedHosts: viper.GetStringSlice(FlagAllowedHosts),
AllowedOrigins: viper.GetStringSlice(FlagAllowedOrigins),
AgentType: agentType,
Process: process,
Port: port,
InteractionType: interactionType,
ChatBasePath: viper.GetString(FlagChatBasePath),
AllowedHosts: viper.GetStringSlice(FlagAllowedHosts),
AllowedOrigins: viper.GetStringSlice(FlagAllowedOrigins),
})
if err != nil {
return xerrors.Errorf("failed to create server: %w", err)
Expand All @@ -118,7 +131,6 @@ func runServer(ctx context.Context, logger *slog.Logger, argsToPass []string) er
fmt.Println(srv.GetOpenAPI())
return nil
}
srv.StartSnapshotLoop(ctx)
logger.Info("Starting server on port", "port", port)
processExitCh := make(chan error, 1)
go func() {
Expand Down Expand Up @@ -163,15 +175,16 @@ type flagSpec struct {
}

const (
FlagType = "type"
FlagPort = "port"
FlagPrintOpenAPI = "print-openapi"
FlagChatBasePath = "chat-base-path"
FlagTermWidth = "term-width"
FlagTermHeight = "term-height"
FlagAllowedHosts = "allowed-hosts"
FlagAllowedOrigins = "allowed-origins"
FlagExit = "exit"
FlagType = "type"
FlagPort = "port"
FlagPrintOpenAPI = "print-openapi"
FlagChatBasePath = "chat-base-path"
FlagTermWidth = "term-width"
FlagTermHeight = "term-height"
FlagAllowedHosts = "allowed-hosts"
FlagAllowedOrigins = "allowed-origins"
FlagExit = "exit"
FlagInteractionType = "interaction"
)

func CreateServerCmd() *cobra.Command {
Expand Down
6 changes: 3 additions & 3 deletions lib/httpapi/claude.go → lib/cli/claude.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package httpapi
package cli

import (
mf "github.com/coder/agentapi/lib/msgfmt"
st "github.com/coder/agentapi/lib/screentracker"
mf "github.com/coder/agentapi/lib/cli/msgfmt"
st "github.com/coder/agentapi/lib/cli/screentracker"
)

func formatPaste(message string) []st.MessagePart {
Expand Down
Loading