Documentation
¶
Index ¶
- Constants
- Variables
- func AccessLogMiddleware(next http.Handler) http.Handler
- func BasicAuthMiddleware(credentials map[string]string) func(http.Handler) http.Handler
- func Execute()
- type ApprovalItem
- type ApprovalResponsePayload
- type ChatRequest
- type ChatState
- type FilePayload
- type MultilineState
- type SessionInfo
- type SessionManager
- func (sm *SessionManager) AddSession(sessionID string, chatName string, chatSession *chatbot.ChatSession)
- func (sm *SessionManager) CloseAllSessions()
- func (sm *SessionManager) GetChatState(sessionID string, chatName string) (*ChatState, bool)
- func (sm *SessionManager) GetSession(sessionID string) (*SessionInfo, bool)
- func (sm *SessionManager) RemoveSession(sessionID string)
- func (sm *SessionManager) UpdateChatSessionWithBot(sessionID string, chatName string, chatSession *chatbot.ChatSession, ...)
- type WebSocketHandler
Constants ¶
const ( DefaultMaxIterations int = 20 DefaultMaxRetries int = 5 )
Variables ¶
var RootCmd = &cobra.Command{ Use: "chat-agent", Short: "Chat Agent CLI tool", Long: `A command line interface for llm agent`, RunE: func(cmd *cobra.Command, args []string) error { if err := logger.Init(); err != nil { return err } cfg, err := config.LoadConfig(configPath) if err != nil { return err } availableChats = cfg.Chats chatName, _ := cmd.Flags().GetString("chat") debug, _ := cmd.Flags().GetBool("debug") if chatName == "" { for name, item := range cfg.Chats { if item.Default { chatName = name break } } } if chatName == "" { return fmt.Errorf("Please specify the chat") } currentChatName = chatName cwd, err := os.Getwd() if err != nil { cwd = "." } sessionID := filepath.Base(cwd) if sessionID == "" || sessionID == "/" { sessionID = "cli-root" } session, err := chatbot.InitChatSession(cmd.Context(), cfg, chatName, sessionID, debug) if err != nil { return err } defer func() { if session != nil { if err := session.Close(); err != nil { fmt.Printf("Error closing session: %v\n", err) } } }() placeholder := "Send a message (/h for help)" homeDir, err := os.UserHomeDir() if err != nil { return err } historyPath := filepath.Join(homeDir, ".chat-agent", "history") scanner, err := readline.New(readline.Prompt{ Prompt: ">>> ", AltPrompt: "... ", Placeholder: placeholder, AltPlaceholder: `Use """ to end multi-line input`, }, readline.WithHistoryFile(historyPath)) if err != nil { return err } scanner.UnsetRawMode() fmt.Print(readline.StartBracketedPaste) defer fmt.Printf(readline.EndBracketedPaste) welcome, _ := cmd.Flags().GetString("welcome") fmt.Printf("%s\n", welcome) msgCount := session.GetMessageCount() if msgCount > 0 { fmt.Printf("[Context restored from previous session: %d messages]\n", msgCount) } persistenceStore := session.PersistenceStore() cb := chatbot.NewChatBot(context.WithValue(cmd.Context(), "debug", debug), session.Agent, session.Manager, scanner, persistenceStore) // ignore ctrl+c and break llm generate var chatCancel context.CancelFunc = func() {} sigChan := make(chan os.Signal, 1) signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM) go func() { for { <-sigChan chatCancel() } }() chatctx, cancel := context.WithCancel(cmd.Context()) chatCancel = cancel if startAt != "" { err = cb.StreamChat(chatctx, startAt) if err != nil { os.Stderr.WriteString("\nerror: " + err.Error() + "\n") return nil } } else if once != "" { err = cb.StreamChat(chatctx, once) if err != nil { os.Stderr.WriteString("\nerror: " + err.Error() + "\n") } return nil } // chat loop var sb strings.Builder var multiline MultilineState for { if scanner.Prompt.Placeholder != placeholder { scanner.Prompt.Placeholder = placeholder scanner.HistoryEnable() } line, err := scanner.Readline() switch { case errors.Is(err, io.EOF): fmt.Println() return nil case errors.Is(err, readline.ErrInterrupt): if line == "" { fmt.Println("\nUse Ctrl + d or /q to exit.") } scanner.Prompt.UseAlt = false sb.Reset() continue case err != nil: return err } switch { case multiline != MultilineNone: before, ok := strings.CutSuffix(line, `"""`) sb.WriteString(before) if !ok { fmt.Fprintln(&sb) continue } multiline = MultilineNone scanner.Prompt.UseAlt = false case strings.HasPrefix(line, `"""`): line := strings.TrimPrefix(line, `"""`) line, ok := strings.CutSuffix(line, `"""`) sb.WriteString(line) if !ok { fmt.Fprintln(&sb) multiline = MultilinePrompt scanner.Prompt.UseAlt = true } case scanner.Pasting: fmt.Fprintln(&sb, line) continue default: sb.WriteString(line) } if sb.Len() > 0 && multiline == MultilineNone { chatctx, cancel := context.WithCancel(cmd.Context()) chatCancel = cancel input := strings.TrimSpace(sb.String()) if !disableLocalCommand && strings.HasPrefix(input, "/t ") { localcmd := strings.TrimSpace(strings.TrimPrefix(input, "/t")) if err := utils.PopenStream(chatctx, localcmd); err != nil { os.Stderr.WriteString("exec local cmd error: " + err.Error() + "\n") } sb.Reset() continue } if strings.HasPrefix(input, "/s ") { targetName := strings.TrimSpace(strings.TrimPrefix(input, "/s")) if targetName == "" { printChats() } else if newSession, err := switchChat(cmd.Context(), cfg, targetName, debug, session, sessionID); err != nil { fmt.Printf("Error switching chat: %v\n", err) } else { session = newSession currentChatName = targetName persistenceStore := session.PersistenceStore() cb = chatbot.NewChatBot(context.WithValue(cmd.Context(), "debug", debug), session.Agent, session.Manager, scanner, persistenceStore) fmt.Printf("Switched to chat: %s\n", targetName) } sb.Reset() continue } switch input { case "/help", "/h": printHelp() case "/clear", "/c": session.Clear() fmt.Println("The conversation context is cleared") case "/redo", "/r": lastMsg := session.GetLastUserMessage() if lastMsg == "" { fmt.Println("No previous user message to redo") } else { session.RemoveLastRound() fmt.Printf("Redoing last message: %s\n", lastMsg) chatctx, cancel := context.WithCancel(cmd.Context()) chatCancel = cancel err = cb.StreamChat(chatctx, lastMsg) session, cb = handleStreamError(err, cmd.Context(), cfg, debug, session, sessionID, scanner, cb) } case "/keep", "/k": if err := session.OnKeep(); err != nil { fmt.Printf("Error executing keep hook: %v\n", err) } else { fmt.Println("Session keep hook executed successfully") } case "/history", "/i": os.Stdout.WriteString(session.Manager.GetSummary()) fmt.Println() case "/debug-context": for _, msg := range session.Manager.GetMessages() { fmt.Println(msg) fmt.Println("===") } case "/tools", "/l": printTools(session.Tools) case "/chat": printChats() case "/quit", "/exit", "/bye", "/q": os.Stdout.WriteString("bye!\n") return nil default: err = cb.StreamChat(chatctx, input) session, cb = handleStreamError(err, cmd.Context(), cfg, debug, session, sessionID, scanner, cb) } sb.Reset() } } }, }
RootCmd represents the base command when called without any subcommands
Functions ¶
func AccessLogMiddleware ¶
AccessLogMiddleware logs each HTTP request in a combined-log-like format. If basic auth is active, the authenticated username is included.
func BasicAuthMiddleware ¶
BasicAuthMiddleware creates a middleware for HTTP Basic Authentication. It accepts a map of username->password pairs and authenticates against any of them. On successful auth, the username is stored in the request context under authUserKey.
Types ¶
type ApprovalItem ¶
type ApprovalItem struct {
Approved bool `json:"approved"`
Reason string `json:"reason,omitempty"`
}
ApprovalItem represents a single approval result
type ApprovalResponsePayload ¶
type ApprovalResponsePayload struct {
ApprovalID string `json:"approval_id"`
Results map[string]ApprovalItem `json:"results"`
}
ApprovalResponsePayload represents the approval response from the client
type ChatRequest ¶
type ChatRequest struct {
ChatName string `json:"chat_name"`
Message string `json:"message"`
Files []FilePayload `json:"files,omitempty"`
}
type ChatState ¶
type ChatState struct {
ChatSession *chatbot.ChatSession
ChatBot *chatbot.ChatBot
}
ChatState represents the state of a single chat within a session
type FilePayload ¶
type FilePayload struct {
URL string `json:"url"`
Type string `json:"type"`
Name string `json:"name"`
FileSize int64 `json:"file_size,omitempty"`
}
FilePayload represents a file in the chat request
type MultilineState ¶
type MultilineState int
const ( MultilineNone MultilineState = iota MultilinePrompt )
type SessionInfo ¶
type SessionManager ¶
type SessionManager struct {
// contains filtered or unexported fields
}
SessionManager manages chat sessions
func NewSessionManager ¶
func NewSessionManager(cfg *config.Config) *SessionManager
func (*SessionManager) AddSession ¶
func (sm *SessionManager) AddSession(sessionID string, chatName string, chatSession *chatbot.ChatSession)
func (*SessionManager) CloseAllSessions ¶
func (sm *SessionManager) CloseAllSessions()
func (*SessionManager) GetChatState ¶
func (sm *SessionManager) GetChatState(sessionID string, chatName string) (*ChatState, bool)
GetChatState gets the chat state for a specific chat in a session
func (*SessionManager) GetSession ¶
func (sm *SessionManager) GetSession(sessionID string) (*SessionInfo, bool)
func (*SessionManager) RemoveSession ¶
func (sm *SessionManager) RemoveSession(sessionID string)
func (*SessionManager) UpdateChatSessionWithBot ¶
func (sm *SessionManager) UpdateChatSessionWithBot(sessionID string, chatName string, chatSession *chatbot.ChatSession, chatBot *chatbot.ChatBot)
UpdateChatSessionWithBot updates session with both ChatSession and ChatBot for a specific chat
type WebSocketHandler ¶
type WebSocketHandler struct {
// contains filtered or unexported fields
}
WebSocketHandler handles WebSocket connections
func NewWebSocketHandler ¶
func NewWebSocketHandler(cfg *config.Config) *WebSocketHandler
NewWebSocketHandler creates a new WebSocket handler
func (*WebSocketHandler) CloseAllSessions ¶
func (h *WebSocketHandler) CloseAllSessions()
func (*WebSocketHandler) HandleWebSocket ¶
func (h *WebSocketHandler) HandleWebSocket(w http.ResponseWriter, r *http.Request)
HandleWebSocket handles a WebSocket connection