Detailed changes
@@ -23,7 +23,6 @@ const DummyHost = "api.crush.localhost"
// Client represents an RPC client connected to a Crush server.
type Client struct {
h *http.Client
- id string
path string
network string
addr string
@@ -62,16 +61,6 @@ func NewClient(path, network, address string) (*Client, error) {
return c, nil
}
-// ID returns the client's instance unique identifier.
-func (c *Client) ID() string {
- return c.id
-}
-
-// SetID sets the client's instance unique identifier.
-func (c *Client) SetID(id string) {
- c.id = id
-}
-
// Path returns the client's instance filesystem path.
func (c *Client) Path() string {
return c.path
@@ -22,9 +22,9 @@ import (
"github.com/charmbracelet/x/powernap/pkg/lsp/protocol"
)
-func (c *Client) SubscribeEvents(ctx context.Context) (<-chan any, error) {
+func (c *Client) SubscribeEvents(ctx context.Context, id string) (<-chan any, error) {
events := make(chan any, 100)
- rsp, err := c.get(ctx, fmt.Sprintf("/instances/%s/events", c.id), nil, http.Header{
+ rsp, err := c.get(ctx, fmt.Sprintf("/instances/%s/events", id), nil, http.Header{
"Accept": []string{"text/event-stream"},
"Cache-Control": []string{"no-cache"},
"Connection": []string{"keep-alive"},
@@ -143,8 +143,8 @@ func sendEvent(ctx context.Context, evc chan any, ev any) {
}
}
-func (c *Client) GetLSPDiagnostics(ctx context.Context, lsp string) (map[protocol.DocumentURI][]protocol.Diagnostic, error) {
- rsp, err := c.get(ctx, fmt.Sprintf("/instances/%s/lsps/%s/diagnostics", c.id, lsp), nil, nil)
+func (c *Client) GetLSPDiagnostics(ctx context.Context, id string, lsp string) (map[protocol.DocumentURI][]protocol.Diagnostic, error) {
+ rsp, err := c.get(ctx, fmt.Sprintf("/instances/%s/lsps/%s/diagnostics", id, lsp), nil, nil)
if err != nil {
return nil, fmt.Errorf("failed to get LSP diagnostics: %w", err)
}
@@ -159,8 +159,8 @@ func (c *Client) GetLSPDiagnostics(ctx context.Context, lsp string) (map[protoco
return diagnostics, nil
}
-func (c *Client) GetLSPs(ctx context.Context) (map[string]app.LSPClientInfo, error) {
- rsp, err := c.get(ctx, fmt.Sprintf("/instances/%s/lsps", c.id), nil, nil)
+func (c *Client) GetLSPs(ctx context.Context, id string) (map[string]app.LSPClientInfo, error) {
+ rsp, err := c.get(ctx, fmt.Sprintf("/instances/%s/lsps", id), nil, nil)
if err != nil {
return nil, fmt.Errorf("failed to get LSPs: %w", err)
}
@@ -175,8 +175,8 @@ func (c *Client) GetLSPs(ctx context.Context) (map[string]app.LSPClientInfo, err
return lsps, nil
}
-func (c *Client) GetAgentSessionQueuedPrompts(ctx context.Context, sessionID string) (int, error) {
- rsp, err := c.get(ctx, fmt.Sprintf("/instances/%s/agent/sessions/%s/prompts/queued", c.id, sessionID), nil, nil)
+func (c *Client) GetAgentSessionQueuedPrompts(ctx context.Context, id string, sessionID string) (int, error) {
+ rsp, err := c.get(ctx, fmt.Sprintf("/instances/%s/agent/sessions/%s/prompts/queued", id, sessionID), nil, nil)
if err != nil {
return 0, fmt.Errorf("failed to get session agent queued prompts: %w", err)
}
@@ -191,8 +191,8 @@ func (c *Client) GetAgentSessionQueuedPrompts(ctx context.Context, sessionID str
return count, nil
}
-func (c *Client) ClearAgentSessionQueuedPrompts(ctx context.Context, sessionID string) error {
- rsp, err := c.post(ctx, fmt.Sprintf("/instances/%s/agent/sessions/%s/prompts/clear", c.id, sessionID), nil, nil, nil)
+func (c *Client) ClearAgentSessionQueuedPrompts(ctx context.Context, id string, sessionID string) error {
+ rsp, err := c.post(ctx, fmt.Sprintf("/instances/%s/agent/sessions/%s/prompts/clear", id, sessionID), nil, nil, nil)
if err != nil {
return fmt.Errorf("failed to clear session agent queued prompts: %w", err)
}
@@ -203,8 +203,8 @@ func (c *Client) ClearAgentSessionQueuedPrompts(ctx context.Context, sessionID s
return nil
}
-func (c *Client) GetAgentInfo(ctx context.Context) (*proto.AgentInfo, error) {
- rsp, err := c.get(ctx, fmt.Sprintf("/instances/%s/agent", c.id), nil, nil)
+func (c *Client) GetAgentInfo(ctx context.Context, id string) (*proto.AgentInfo, error) {
+ rsp, err := c.get(ctx, fmt.Sprintf("/instances/%s/agent", id), nil, nil)
if err != nil {
return nil, fmt.Errorf("failed to get agent status: %w", err)
}
@@ -219,8 +219,8 @@ func (c *Client) GetAgentInfo(ctx context.Context) (*proto.AgentInfo, error) {
return &info, nil
}
-func (c *Client) UpdateAgent(ctx context.Context) error {
- rsp, err := c.post(ctx, fmt.Sprintf("/instances/%s/agent/update", c.id), nil, nil, nil)
+func (c *Client) UpdateAgent(ctx context.Context, id string) error {
+ rsp, err := c.post(ctx, fmt.Sprintf("/instances/%s/agent/update", id), nil, nil, nil)
if err != nil {
return fmt.Errorf("failed to update agent: %w", err)
}
@@ -231,8 +231,8 @@ func (c *Client) UpdateAgent(ctx context.Context) error {
return nil
}
-func (c *Client) SendMessage(ctx context.Context, sessionID, message string, attchments ...message.Attachment) error {
- rsp, err := c.post(ctx, fmt.Sprintf("/instances/%s/agent", c.id), nil, jsonBody(proto.AgentMessage{
+func (c *Client) SendMessage(ctx context.Context, id string, sessionID, message string, attchments ...message.Attachment) error {
+ rsp, err := c.post(ctx, fmt.Sprintf("/instances/%s/agent", id), nil, jsonBody(proto.AgentMessage{
SessionID: sessionID,
Prompt: message,
Attachments: attchments,
@@ -247,8 +247,8 @@ func (c *Client) SendMessage(ctx context.Context, sessionID, message string, att
return nil
}
-func (c *Client) GetAgentSessionInfo(ctx context.Context, sessionID string) (*proto.AgentSession, error) {
- rsp, err := c.get(ctx, fmt.Sprintf("/instances/%s/agent/sessions/%s", c.id, sessionID), nil, nil)
+func (c *Client) GetAgentSessionInfo(ctx context.Context, id string, sessionID string) (*proto.AgentSession, error) {
+ rsp, err := c.get(ctx, fmt.Sprintf("/instances/%s/agent/sessions/%s", id, sessionID), nil, nil)
if err != nil {
return nil, fmt.Errorf("failed to get session agent info: %w", err)
}
@@ -263,8 +263,8 @@ func (c *Client) GetAgentSessionInfo(ctx context.Context, sessionID string) (*pr
return &info, nil
}
-func (c *Client) AgentSummarizeSession(ctx context.Context, sessionID string) error {
- rsp, err := c.post(ctx, fmt.Sprintf("/instances/%s/agent/sessions/%s/summarize", c.id, sessionID), nil, nil, nil)
+func (c *Client) AgentSummarizeSession(ctx context.Context, id string, sessionID string) error {
+ rsp, err := c.post(ctx, fmt.Sprintf("/instances/%s/agent/sessions/%s/summarize", id, sessionID), nil, nil, nil)
if err != nil {
return fmt.Errorf("failed to summarize session: %w", err)
}
@@ -275,8 +275,8 @@ func (c *Client) AgentSummarizeSession(ctx context.Context, sessionID string) er
return nil
}
-func (c *Client) ListMessages(ctx context.Context, sessionID string) ([]message.Message, error) {
- rsp, err := c.get(ctx, fmt.Sprintf("/instances/%s/sessions/%s/messages", c.id, sessionID), nil, nil)
+func (c *Client) ListMessages(ctx context.Context, id string, sessionID string) ([]message.Message, error) {
+ rsp, err := c.get(ctx, fmt.Sprintf("/instances/%s/sessions/%s/messages", id, sessionID), nil, nil)
if err != nil {
return nil, fmt.Errorf("failed to get messages: %w", err)
}
@@ -291,8 +291,8 @@ func (c *Client) ListMessages(ctx context.Context, sessionID string) ([]message.
return messages, nil
}
-func (c *Client) GetSession(ctx context.Context, sessionID string) (*session.Session, error) {
- rsp, err := c.get(ctx, fmt.Sprintf("/instances/%s/sessions/%s", c.id, sessionID), nil, nil)
+func (c *Client) GetSession(ctx context.Context, id string, sessionID string) (*session.Session, error) {
+ rsp, err := c.get(ctx, fmt.Sprintf("/instances/%s/sessions/%s", id, sessionID), nil, nil)
if err != nil {
return nil, fmt.Errorf("failed to get session: %w", err)
}
@@ -307,8 +307,8 @@ func (c *Client) GetSession(ctx context.Context, sessionID string) (*session.Ses
return &sess, nil
}
-func (c *Client) InitiateAgentProcessing(ctx context.Context) error {
- rsp, err := c.post(ctx, fmt.Sprintf("/instances/%s/agent/init", c.id), nil, nil, nil)
+func (c *Client) InitiateAgentProcessing(ctx context.Context, id string) error {
+ rsp, err := c.post(ctx, fmt.Sprintf("/instances/%s/agent/init", id), nil, nil, nil)
if err != nil {
return fmt.Errorf("failed to initiate session agent processing: %w", err)
}
@@ -319,8 +319,8 @@ func (c *Client) InitiateAgentProcessing(ctx context.Context) error {
return nil
}
-func (c *Client) ListSessionHistoryFiles(ctx context.Context, sessionID string) ([]history.File, error) {
- rsp, err := c.get(ctx, fmt.Sprintf("/instances/%s/sessions/%s/history", c.id, sessionID), nil, nil)
+func (c *Client) ListSessionHistoryFiles(ctx context.Context, id string, sessionID string) ([]history.File, error) {
+ rsp, err := c.get(ctx, fmt.Sprintf("/instances/%s/sessions/%s/history", id, sessionID), nil, nil)
if err != nil {
return nil, fmt.Errorf("failed to get session history files: %w", err)
}
@@ -335,8 +335,8 @@ func (c *Client) ListSessionHistoryFiles(ctx context.Context, sessionID string)
return files, nil
}
-func (c *Client) CreateSession(ctx context.Context, title string) (*session.Session, error) {
- rsp, err := c.post(ctx, fmt.Sprintf("/instances/%s/sessions", c.id), nil, jsonBody(session.Session{Title: title}), http.Header{"Content-Type": []string{"application/json"}})
+func (c *Client) CreateSession(ctx context.Context, id string, title string) (*session.Session, error) {
+ rsp, err := c.post(ctx, fmt.Sprintf("/instances/%s/sessions", id), nil, jsonBody(session.Session{Title: title}), http.Header{"Content-Type": []string{"application/json"}})
if err != nil {
return nil, fmt.Errorf("failed to create session: %w", err)
}
@@ -351,8 +351,8 @@ func (c *Client) CreateSession(ctx context.Context, title string) (*session.Sess
return &sess, nil
}
-func (c *Client) ListSessions(ctx context.Context) ([]session.Session, error) {
- rsp, err := c.get(ctx, fmt.Sprintf("/instances/%s/sessions", c.id), nil, nil)
+func (c *Client) ListSessions(ctx context.Context, id string) ([]session.Session, error) {
+ rsp, err := c.get(ctx, fmt.Sprintf("/instances/%s/sessions", id), nil, nil)
if err != nil {
return nil, fmt.Errorf("failed to get sessions: %w", err)
}
@@ -367,8 +367,8 @@ func (c *Client) ListSessions(ctx context.Context) ([]session.Session, error) {
return sessions, nil
}
-func (c *Client) GrantPermission(ctx context.Context, req proto.PermissionGrant) error {
- rsp, err := c.post(ctx, fmt.Sprintf("/instances/%s/permissions/grant", c.id), nil, jsonBody(req), http.Header{"Content-Type": []string{"application/json"}})
+func (c *Client) GrantPermission(ctx context.Context, id string, req proto.PermissionGrant) error {
+ rsp, err := c.post(ctx, fmt.Sprintf("/instances/%s/permissions/grant", id), nil, jsonBody(req), http.Header{"Content-Type": []string{"application/json"}})
if err != nil {
return fmt.Errorf("failed to grant permission: %w", err)
}
@@ -379,8 +379,8 @@ func (c *Client) GrantPermission(ctx context.Context, req proto.PermissionGrant)
return nil
}
-func (c *Client) SetPermissionsSkipRequests(ctx context.Context, skip bool) error {
- rsp, err := c.post(ctx, fmt.Sprintf("/instances/%s/permissions/skip", c.id), nil, jsonBody(proto.PermissionSkipRequest{Skip: skip}), http.Header{"Content-Type": []string{"application/json"}})
+func (c *Client) SetPermissionsSkipRequests(ctx context.Context, id string, skip bool) error {
+ rsp, err := c.post(ctx, fmt.Sprintf("/instances/%s/permissions/skip", id), nil, jsonBody(proto.PermissionSkipRequest{Skip: skip}), http.Header{"Content-Type": []string{"application/json"}})
if err != nil {
return fmt.Errorf("failed to set permissions skip requests: %w", err)
}
@@ -391,8 +391,8 @@ func (c *Client) SetPermissionsSkipRequests(ctx context.Context, skip bool) erro
return nil
}
-func (c *Client) GetPermissionsSkipRequests(ctx context.Context) (bool, error) {
- rsp, err := c.get(ctx, fmt.Sprintf("/instances/%s/permissions/skip", c.id), nil, nil)
+func (c *Client) GetPermissionsSkipRequests(ctx context.Context, id string) (bool, error) {
+ rsp, err := c.get(ctx, fmt.Sprintf("/instances/%s/permissions/skip", id), nil, nil)
if err != nil {
return false, fmt.Errorf("failed to get permissions skip requests: %w", err)
}
@@ -407,8 +407,8 @@ func (c *Client) GetPermissionsSkipRequests(ctx context.Context) (bool, error) {
return skip.Skip, nil
}
-func (c *Client) GetConfig(ctx context.Context) (*config.Config, error) {
- rsp, err := c.get(ctx, fmt.Sprintf("/instances/%s/config", c.id), nil, nil)
+func (c *Client) GetConfig(ctx context.Context, id string) (*config.Config, error) {
+ rsp, err := c.get(ctx, fmt.Sprintf("/instances/%s/config", id), nil, nil)
if err != nil {
return nil, fmt.Errorf("failed to get config: %w", err)
}
@@ -108,7 +108,7 @@ crush -y
// TODO: implement TCP support
}
- c, err := setupApp(cmd, hostURL)
+ c, ins, err := setupApp(cmd, hostURL)
if err != nil {
return err
}
@@ -128,12 +128,12 @@ crush -y
return fmt.Errorf("failed to connect to crush server: %v", err)
}
- m, err := tui.New(c)
+ m, err := tui.New(c, ins)
if err != nil {
return fmt.Errorf("failed to create TUI model: %v", err)
}
- defer func() { c.DeleteInstance(cmd.Context(), c.ID()) }()
+ defer func() { c.DeleteInstance(cmd.Context(), ins.ID) }()
event.AppInitialized()
@@ -146,7 +146,7 @@ crush -y
tea.WithFilter(tui.MouseEventFilter), // Filter mouse events based on focus state
)
- evc, err := c.SubscribeEvents(cmd.Context())
+ evc, err := c.SubscribeEvents(cmd.Context(), ins.ID)
if err != nil {
return fmt.Errorf("failed to subscribe to events: %v", err)
}
@@ -199,7 +199,7 @@ func streamEvents(ctx context.Context, evc <-chan any, p *tea.Program) {
// setupApp handles the common setup logic for both interactive and non-interactive modes.
// It returns the app instance, config, cleanup function, and any error.
-func setupApp(cmd *cobra.Command, hostURL *url.URL) (*client.Client, error) {
+func setupApp(cmd *cobra.Command, hostURL *url.URL) (*client.Client, *proto.Instance, error) {
debug, _ := cmd.Flags().GetBool("debug")
yolo, _ := cmd.Flags().GetBool("yolo")
dataDir, _ := cmd.Flags().GetString("data-dir")
@@ -207,12 +207,12 @@ func setupApp(cmd *cobra.Command, hostURL *url.URL) (*client.Client, error) {
cwd, err := ResolveCwd(cmd)
if err != nil {
- return nil, err
+ return nil, nil, err
}
c, err := client.NewClient(cwd, hostURL.Scheme, hostURL.Host)
if err != nil {
- return nil, err
+ return nil, nil, err
}
ins, err := c.CreateInstance(ctx, proto.Instance{
@@ -222,21 +222,19 @@ func setupApp(cmd *cobra.Command, hostURL *url.URL) (*client.Client, error) {
YOLO: yolo,
})
if err != nil {
- return nil, fmt.Errorf("failed to create or connect to instance: %v", err)
+ return nil, nil, fmt.Errorf("failed to create or connect to instance: %v", err)
}
- c.SetID(ins.ID)
-
cfg, err := c.GetGlobalConfig(cmd.Context())
if err != nil {
- return nil, fmt.Errorf("failed to get global config: %v", err)
+ return nil, nil, fmt.Errorf("failed to get global config: %v", err)
}
if shouldEnableMetrics(cfg) {
event.Init()
}
- return c, nil
+ return c, ins, nil
}
var safeNameRegexp = regexp.MustCompile(`[^a-zA-Z0-9._-]`)
@@ -31,13 +31,13 @@ crush run -q "Generate a README for this project"
return fmt.Errorf("invalid host URL: %v", err)
}
- c, err := setupApp(cmd, hostURL)
+ c, ins, err := setupApp(cmd, hostURL)
if err != nil {
return err
}
- defer func() { c.DeleteInstance(cmd.Context(), c.ID()) }()
+ defer func() { c.DeleteInstance(cmd.Context(), ins.ID) }()
- cfg, err := c.GetConfig(cmd.Context())
+ cfg, err := c.GetConfig(cmd.Context(), ins.ID)
if err != nil {
return fmt.Errorf("failed to get config: %v", err)
}
@@ -4,17 +4,19 @@ import (
"time"
"github.com/charmbracelet/catwalk/pkg/catwalk"
+ "github.com/charmbracelet/crush/internal/config"
"github.com/charmbracelet/crush/internal/lsp"
)
// Instance represents a running app.App instance with its associated resources
// and state.
type Instance struct {
- ID string `json:"id"`
- Path string `json:"path"`
- YOLO bool `json:"yolo,omitempty"`
- Debug bool `json:"debug,omitempty"`
- DataDir string `json:"data_dir,omitempty"`
+ ID string `json:"id"`
+ Path string `json:"path"`
+ YOLO bool `json:"yolo,omitempty"`
+ Debug bool `json:"debug,omitempty"`
+ DataDir string `json:"data_dir,omitempty"`
+ Config *config.Config `json:"config,omitempty"`
}
// Error represents an error response.
@@ -67,12 +67,14 @@ func (c *controllerV1) handleGetConfig(w http.ResponseWriter, r *http.Request) {
func (c *controllerV1) handleGetInstances(w http.ResponseWriter, r *http.Request) {
instances := []proto.Instance{}
for _, ins := range c.instances.Seq2() {
+ // TODO: implement pagination?
instances = append(instances, proto.Instance{
- ID: ins.ID(),
- Path: ins.Path(),
+ ID: ins.id,
+ Path: ins.path,
YOLO: ins.cfg.Permissions != nil && ins.cfg.Permissions.SkipRequests,
DataDir: ins.cfg.Options.DataDirectory,
Debug: ins.cfg.Options.Debug,
+ Config: ins.cfg,
})
}
jsonEncode(w, instances)
@@ -512,6 +514,25 @@ func (c *controllerV1) handleDeleteInstances(w http.ResponseWriter, r *http.Requ
c.instances.Del(id)
}
+func (c *controllerV1) handleGetInstance(w http.ResponseWriter, r *http.Request) {
+ id := r.PathValue("id")
+ ins, ok := c.instances.Get(id)
+ if !ok {
+ c.logError(r, "instance not found", "id", id)
+ jsonError(w, http.StatusNotFound, "instance not found")
+ return
+ }
+
+ jsonEncode(w, proto.Instance{
+ ID: ins.id,
+ Path: ins.path,
+ YOLO: ins.cfg.Permissions != nil && ins.cfg.Permissions.SkipRequests,
+ DataDir: ins.cfg.Options.DataDirectory,
+ Debug: ins.cfg.Options.Debug,
+ Config: ins.cfg,
+ })
+}
+
func (c *controllerV1) handlePostInstances(w http.ResponseWriter, r *http.Request) {
var args proto.Instance
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
@@ -575,6 +596,7 @@ func (c *controllerV1) handlePostInstances(w http.ResponseWriter, r *http.Reques
DataDir: cfg.Options.DataDirectory,
Debug: cfg.Options.Debug,
YOLO: cfg.Permissions.SkipRequests,
+ Config: cfg,
})
}
@@ -42,16 +42,6 @@ type Instance struct {
path string
}
-// ID returns the unique identifier of the instance.
-func (i *Instance) ID() string {
- return i.id
-}
-
-// Path returns the filesystem path associated with the instance.
-func (i *Instance) Path() string {
- return i.path
-}
-
// ParseHostURL parses a host URL into a [url.URL].
func ParseHostURL(host string) (*url.URL, error) {
proto, addr, ok := strings.Cut(host, "://")
@@ -141,6 +131,7 @@ func NewServer(cfg *config.Config, network, address string) *Server {
mux.HandleFunc("GET /v1/instances", c.handleGetInstances)
mux.HandleFunc("POST /v1/instances", c.handlePostInstances)
mux.HandleFunc("DELETE /v1/instances/{id}", c.handleDeleteInstances)
+ mux.HandleFunc("GET /v1/instances/{id}", c.handleGetInstance)
mux.HandleFunc("GET /v1/instances/{id}/config", c.handleGetInstanceConfig)
mux.HandleFunc("GET /v1/instances/{id}/events", c.handleGetInstanceEvents)
mux.HandleFunc("GET /v1/instances/{id}/sessions", c.handleGetInstanceSessions)
@@ -9,10 +9,10 @@ import (
"github.com/charmbracelet/bubbles/v2/key"
tea "github.com/charmbracelet/bubbletea/v2"
"github.com/charmbracelet/crush/internal/client"
- "github.com/charmbracelet/crush/internal/config"
"github.com/charmbracelet/crush/internal/llm/agent"
"github.com/charmbracelet/crush/internal/message"
"github.com/charmbracelet/crush/internal/permission"
+ "github.com/charmbracelet/crush/internal/proto"
"github.com/charmbracelet/crush/internal/pubsub"
"github.com/charmbracelet/crush/internal/session"
"github.com/charmbracelet/crush/internal/tui/components/chat/messages"
@@ -59,8 +59,8 @@ type MessageListCmp interface {
// of chat messages with support for tool calls, real-time updates, and
// session switching.
type messageListCmp struct {
- client *client.Client
- cfg *config.Config
+ c *client.Client
+ ins *proto.Instance
width, height int
session session.Session
listCmp list.List[list.Item]
@@ -79,7 +79,7 @@ type messageListCmp struct {
// New creates a new message list component with custom keybindings
// and reverse ordering (newest messages at bottom).
-func New(app *client.Client, cfg *config.Config) MessageListCmp {
+func New(app *client.Client, ins *proto.Instance) MessageListCmp {
defaultListKeyMap := list.DefaultKeyMap()
listCmp := list.New(
[]list.Item{},
@@ -90,8 +90,8 @@ func New(app *client.Client, cfg *config.Config) MessageListCmp {
list.WithEnableMouse(),
)
return &messageListCmp{
- client: app,
- cfg: cfg,
+ c: app,
+ ins: ins,
listCmp: listCmp,
previousSelected: "",
defaultListKeyMap: defaultListKeyMap,
@@ -106,9 +106,9 @@ func (m *messageListCmp) Init() tea.Cmd {
// Update handles incoming messages and updates the component state.
func (m *messageListCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
var cmds []tea.Cmd
- info, err := m.client.GetAgentInfo(context.TODO())
+ info, err := m.c.GetAgentInfo(context.TODO(), m.ins.ID)
if m.session.ID != "" && err == nil && !info.IsZero() {
- queueSize, _ := m.client.GetAgentSessionQueuedPrompts(context.TODO(), m.session.ID)
+ queueSize, _ := m.c.GetAgentSessionQueuedPrompts(context.TODO(), m.ins.ID, m.session.ID)
if queueSize != m.promptQueue {
m.promptQueue = queueSize
cmds = append(cmds, m.SetSize(m.width, m.height))
@@ -239,7 +239,7 @@ func (m *messageListCmp) View() string {
m.listCmp.View(),
),
}
- info, err := m.client.GetAgentInfo(context.TODO())
+ info, err := m.c.GetAgentInfo(context.TODO(), m.ins.ID)
if err == nil && !info.IsZero() && m.promptQueue > 0 {
queuePill := queuePill(m.promptQueue, t)
view = append(view, t.S().Base.PaddingLeft(4).PaddingTop(1).Render(queuePill))
@@ -373,7 +373,7 @@ func (m *messageListCmp) handleNewMessage(msg message.Message) tea.Cmd {
// handleNewUserMessage adds a new user message to the list and updates the timestamp.
func (m *messageListCmp) handleNewUserMessage(msg message.Message) tea.Cmd {
m.lastUserMessageTime = msg.CreatedAt
- return m.listCmp.AppendItem(messages.NewMessageCmp(m.cfg, msg))
+ return m.listCmp.AppendItem(messages.NewMessageCmp(m.ins.Config, msg))
}
// handleToolMessage updates existing tool calls with their results.
@@ -466,7 +466,7 @@ func (m *messageListCmp) updateAssistantMessageContent(msg message.Message, assi
if msg.FinishPart() != nil && msg.FinishPart().Reason == message.FinishReasonEndTurn {
m.listCmp.AppendItem(
messages.NewAssistantSection(
- m.cfg,
+ m.ins.Config,
msg,
time.Unix(m.lastUserMessageTime, 0),
),
@@ -524,7 +524,7 @@ func (m *messageListCmp) handleNewAssistantMessage(msg message.Message) tea.Cmd
if m.shouldShowAssistantMessage(msg) {
cmd := m.listCmp.AppendItem(
messages.NewMessageCmp(
- m.cfg,
+ m.ins.Config,
msg,
),
)
@@ -547,7 +547,7 @@ func (m *messageListCmp) SetSession(session session.Session) tea.Cmd {
}
m.session = session
- sessionMessages, err := m.client.ListMessages(context.Background(), session.ID)
+ sessionMessages, err := m.c.ListMessages(context.Background(), m.ins.ID, session.ID)
if err != nil {
return util.ReportError(err)
}
@@ -587,11 +587,11 @@ func (m *messageListCmp) convertMessagesToUI(sessionMessages []message.Message,
switch msg.Role {
case message.User:
m.lastUserMessageTime = msg.CreatedAt
- uiMessages = append(uiMessages, messages.NewMessageCmp(m.cfg, msg))
+ uiMessages = append(uiMessages, messages.NewMessageCmp(m.ins.Config, msg))
case message.Assistant:
uiMessages = append(uiMessages, m.convertAssistantMessage(msg, toolResultMap)...)
if msg.FinishPart() != nil && msg.FinishPart().Reason == message.FinishReasonEndTurn {
- uiMessages = append(uiMessages, messages.NewAssistantSection(m.cfg, msg, time.Unix(m.lastUserMessageTime, 0)))
+ uiMessages = append(uiMessages, messages.NewAssistantSection(m.ins.Config, msg, time.Unix(m.lastUserMessageTime, 0)))
}
}
}
@@ -608,7 +608,7 @@ func (m *messageListCmp) convertAssistantMessage(msg message.Message, toolResult
uiMessages = append(
uiMessages,
messages.NewMessageCmp(
- m.cfg, msg,
+ m.ins.Config, msg,
),
)
}
@@ -619,7 +619,7 @@ func (m *messageListCmp) convertAssistantMessage(msg message.Message, toolResult
uiMessages = append(uiMessages, messages.NewToolCallCmp(msg.ID, tc, options...))
// If this tool call is the agent tool, fetch nested tool calls
if tc.Name == agent.AgentToolName {
- nestedMessages, _ := m.client.ListMessages(context.Background(), tc.ID)
+ nestedMessages, _ := m.c.ListMessages(context.Background(), m.ins.ID, tc.ID)
nestedToolResultMap := m.buildToolResultMap(nestedMessages)
nestedUIMessages := m.convertMessagesToUI(nestedMessages, nestedToolResultMap)
nestedToolCalls := make([]messages.ToolCallCmp, 0, len(nestedUIMessages))
@@ -19,6 +19,7 @@ import (
"github.com/charmbracelet/crush/internal/client"
"github.com/charmbracelet/crush/internal/fsext"
"github.com/charmbracelet/crush/internal/message"
+ "github.com/charmbracelet/crush/internal/proto"
"github.com/charmbracelet/crush/internal/session"
"github.com/charmbracelet/crush/internal/tui/components/chat"
"github.com/charmbracelet/crush/internal/tui/components/completions"
@@ -53,7 +54,8 @@ type editorCmp struct {
width int
height int
x, y int
- app *client.Client
+ c *client.Client
+ ins *proto.Instance
session session.Session
textarea *textarea.Model
attachments []message.Attachment
@@ -211,7 +213,7 @@ func (m *editorCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
}
case commands.OpenExternalEditorMsg:
- info, err := m.app.GetAgentSessionInfo(context.TODO(), m.session.ID)
+ info, err := m.c.GetAgentSessionInfo(context.TODO(), m.ins.ID, m.session.ID)
if err == nil && info.IsBusy {
return m, util.ReportWarn("Agent is working, please wait...")
}
@@ -298,7 +300,7 @@ func (m *editorCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
}
}
if key.Matches(msg, m.keyMap.OpenEditor) {
- info, err := m.app.GetAgentSessionInfo(context.TODO(), m.session.ID)
+ info, err := m.c.GetAgentSessionInfo(context.TODO(), m.ins.ID, m.session.ID)
if err == nil && info.IsBusy {
return m, util.ReportWarn("Agent is working, please wait...")
}
@@ -367,7 +369,7 @@ func (m *editorCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
}
func (m *editorCmp) setEditorPrompt() {
- skip, err := m.app.GetPermissionsSkipRequests(context.TODO())
+ skip, err := m.c.GetPermissionsSkipRequests(context.TODO(), m.ins.ID)
if err == nil && skip {
m.textarea.SetPromptFunc(4, yoloPromptFunc)
return
@@ -418,13 +420,13 @@ func (m *editorCmp) randomizePlaceholders() {
func (m *editorCmp) View() string {
t := styles.CurrentTheme()
// Update placeholder
- info, err := m.app.GetAgentInfo(context.TODO())
+ info, err := m.c.GetAgentInfo(context.TODO(), m.ins.ID)
if err == nil && info.IsBusy {
m.textarea.Placeholder = m.workingPlaceholder
} else {
m.textarea.Placeholder = m.readyPlaceholder
}
- skip, err := m.app.GetPermissionsSkipRequests(context.TODO())
+ skip, err := m.c.GetPermissionsSkipRequests(context.TODO(), m.ins.ID)
if err == nil && skip {
m.textarea.Placeholder = "Yolo mode!"
}
@@ -568,7 +570,7 @@ func yoloPromptFunc(info textarea.PromptInfo) string {
return fmt.Sprintf("%s ", t.YoloDotsBlurred)
}
-func New(app *client.Client) Editor {
+func New(c *client.Client, ins *proto.Instance) Editor {
t := styles.CurrentTheme()
ta := textarea.New()
ta.SetStyles(t.S().TextArea)
@@ -578,7 +580,8 @@ func New(app *client.Client) Editor {
ta.Focus()
e := &editorCmp{
// TODO: remove the app instance from here
- app: app,
+ c: c,
+ ins: ins,
textarea: ta,
keyMap: DefaultEditorKeyMap(),
}
@@ -7,8 +7,8 @@ import (
tea "github.com/charmbracelet/bubbletea/v2"
"github.com/charmbracelet/crush/internal/client"
- "github.com/charmbracelet/crush/internal/config"
"github.com/charmbracelet/crush/internal/fsext"
+ "github.com/charmbracelet/crush/internal/proto"
"github.com/charmbracelet/crush/internal/pubsub"
"github.com/charmbracelet/crush/internal/session"
"github.com/charmbracelet/crush/internal/tui/styles"
@@ -30,14 +30,14 @@ type header struct {
width int
session session.Session
client *client.Client
- cfg *config.Config
+ ins *proto.Instance
detailsOpen bool
}
-func New(lspClients *client.Client, cfg *config.Config) Header {
+func New(lspClients *client.Client, ins *proto.Instance) Header {
return &header{
client: lspClients,
- cfg: cfg,
+ ins: ins,
width: 0,
}
}
@@ -108,14 +108,14 @@ func (h *header) details(availWidth int) string {
errorCount := 0
// TODO: Move this to update?
- lsps, err := h.client.GetLSPs(context.TODO())
+ lsps, err := h.client.GetLSPs(context.TODO(), h.ins.ID)
if err != nil {
return ""
}
for l := range lsps {
// TODO: Same here, move to update?
- diags, err := h.client.GetLSPDiagnostics(context.TODO(), l)
+ diags, err := h.client.GetLSPDiagnostics(context.TODO(), h.ins.ID, l)
if err != nil {
return ""
}
@@ -132,8 +132,8 @@ func (h *header) details(availWidth int) string {
parts = append(parts, s.Error.Render(fmt.Sprintf("%s%d", styles.ErrorIcon, errorCount)))
}
- agentCfg := h.cfg.Agents["coder"]
- model := h.cfg.GetModelByType(agentCfg.Model)
+ agentCfg := h.ins.Config.Agents["coder"]
+ model := h.ins.Config.GetModelByType(agentCfg.Model)
if model == nil {
return "No model"
}
@@ -154,7 +154,7 @@ func (h *header) details(availWidth int) string {
// Truncate cwd if necessary, and insert it at the beginning.
const dirTrimLimit = 4
- cwd := fsext.DirTrim(fsext.PrettyPath(h.cfg.WorkingDir()), dirTrimLimit)
+ cwd := fsext.DirTrim(fsext.PrettyPath(h.ins.Config.WorkingDir()), dirTrimLimit)
cwd = ansi.Truncate(cwd, max(0, availWidth-lipgloss.Width(metadata)), "…")
cwd = s.Muted.Render(cwd)
@@ -15,6 +15,7 @@ import (
"github.com/charmbracelet/crush/internal/fsext"
"github.com/charmbracelet/crush/internal/history"
"github.com/charmbracelet/crush/internal/home"
+ "github.com/charmbracelet/crush/internal/proto"
"github.com/charmbracelet/crush/internal/pubsub"
"github.com/charmbracelet/crush/internal/session"
"github.com/charmbracelet/crush/internal/tui/components/chat"
@@ -69,16 +70,16 @@ type sidebarCmp struct {
session session.Session
logo string
cwd string
- client *client.Client
+ c *client.Client
compactMode bool
files *csync.Map[string, SessionFile]
- cfg *config.Config
+ ins *proto.Instance
}
-func New(c *client.Client, cfg *config.Config, compact bool) Sidebar {
+func New(c *client.Client, ins *proto.Instance, compact bool) Sidebar {
return &sidebarCmp{
- client: c,
- cfg: cfg,
+ c: c,
+ ins: ins,
compactMode: compact,
files: csync.NewMap[string, SessionFile](),
}
@@ -194,7 +195,7 @@ func (m *sidebarCmp) handleFileHistoryEvent(event pubsub.Event[history.File]) te
before, _ := fsext.ToUnixLineEndings(existing.History.initialVersion.Content)
after, _ := fsext.ToUnixLineEndings(existing.History.latestVersion.Content)
path := existing.History.initialVersion.Path
- cwd := m.cfg.WorkingDir()
+ cwd := m.ins.Config.WorkingDir()
path = strings.TrimPrefix(path, cwd)
_, additions, deletions := diff.GenerateDiff(before, after, path)
existing.Additions = additions
@@ -221,7 +222,7 @@ func (m *sidebarCmp) handleFileHistoryEvent(event pubsub.Event[history.File]) te
}
func (m *sidebarCmp) loadSessionFiles() tea.Msg {
- files, err := m.client.ListSessionHistoryFiles(context.Background(), m.session.ID)
+ files, err := m.c.ListSessionHistoryFiles(context.Background(), m.ins.ID, m.session.ID)
if err != nil {
return util.InfoMsg{
Type: util.InfoTypeError,
@@ -247,7 +248,7 @@ func (m *sidebarCmp) loadSessionFiles() tea.Msg {
sessionFiles := make([]SessionFile, 0, len(fileMap))
for path, fh := range fileMap {
- cwd := m.cfg.WorkingDir()
+ cwd := m.ins.Config.WorkingDir()
path = strings.TrimPrefix(path, cwd)
before, _ := fsext.ToUnixLineEndings(fh.initialVersion.Content)
after, _ := fsext.ToUnixLineEndings(fh.latestVersion.Content)
@@ -267,7 +268,7 @@ func (m *sidebarCmp) loadSessionFiles() tea.Msg {
func (m *sidebarCmp) SetSize(width, height int) tea.Cmd {
m.logo = m.logoBlock()
- m.cwd = cwd(m.cfg)
+ m.cwd = cwd(m.ins.Config)
m.width = width
m.height = height
return nil
@@ -406,7 +407,7 @@ func (m *sidebarCmp) filesBlockCompact(maxWidth int) string {
maxItems = min(maxItems, availableHeight)
}
- return files.RenderFileBlock(m.cfg, fileSlice, files.RenderOptions{
+ return files.RenderFileBlock(m.ins.Config, fileSlice, files.RenderOptions{
MaxWidth: maxWidth,
MaxItems: maxItems,
ShowSection: true,
@@ -417,14 +418,14 @@ func (m *sidebarCmp) filesBlockCompact(maxWidth int) string {
// lspBlockCompact renders the LSP block with limited width and height for horizontal layout
func (m *sidebarCmp) lspBlockCompact(maxWidth int) string {
// Limit items for horizontal layout
- lspConfigs := m.cfg.LSP.Sorted()
+ lspConfigs := m.ins.Config.LSP.Sorted()
maxItems := min(5, len(lspConfigs))
availableHeight := m.height - 8
if availableHeight > 0 {
maxItems = min(maxItems, availableHeight)
}
- return lspcomponent.RenderLSPBlock(m.client, m.cfg, lspcomponent.RenderOptions{
+ return lspcomponent.RenderLSPBlock(m.c, m.ins, lspcomponent.RenderOptions{
MaxWidth: maxWidth,
MaxItems: maxItems,
ShowSection: true,
@@ -435,13 +436,13 @@ func (m *sidebarCmp) lspBlockCompact(maxWidth int) string {
// mcpBlockCompact renders the MCP block with limited width and height for horizontal layout
func (m *sidebarCmp) mcpBlockCompact(maxWidth int) string {
// Limit items for horizontal layout
- maxItems := min(5, len(m.cfg.MCP.Sorted()))
+ maxItems := min(5, len(m.ins.Config.MCP.Sorted()))
availableHeight := m.height - 8
if availableHeight > 0 {
maxItems = min(maxItems, availableHeight)
}
- return mcp.RenderMCPBlock(m.cfg, mcp.RenderOptions{
+ return mcp.RenderMCPBlock(m.ins.Config, mcp.RenderOptions{
MaxWidth: maxWidth,
MaxItems: maxItems,
ShowSection: true,
@@ -469,7 +470,7 @@ func (m *sidebarCmp) filesBlock() string {
maxFiles, _, _ := m.getDynamicLimits()
maxFiles = min(len(fileSlice), maxFiles)
- return files.RenderFileBlock(m.cfg, fileSlice, files.RenderOptions{
+ return files.RenderFileBlock(m.ins.Config, fileSlice, files.RenderOptions{
MaxWidth: m.getMaxWidth(),
MaxItems: maxFiles,
ShowSection: true,
@@ -480,10 +481,10 @@ func (m *sidebarCmp) filesBlock() string {
func (m *sidebarCmp) lspBlock() string {
// Limit the number of LSPs shown
_, maxLSPs, _ := m.getDynamicLimits()
- lspConfigs := m.cfg.LSP.Sorted()
+ lspConfigs := m.ins.Config.LSP.Sorted()
maxLSPs = min(len(lspConfigs), maxLSPs)
- return lspcomponent.RenderLSPBlock(m.client, m.cfg, lspcomponent.RenderOptions{
+ return lspcomponent.RenderLSPBlock(m.c, m.ins, lspcomponent.RenderOptions{
MaxWidth: m.getMaxWidth(),
MaxItems: maxLSPs,
ShowSection: true,
@@ -494,10 +495,10 @@ func (m *sidebarCmp) lspBlock() string {
func (m *sidebarCmp) mcpBlock() string {
// Limit the number of MCPs shown
_, _, maxMCPs := m.getDynamicLimits()
- mcps := m.cfg.MCP.Sorted()
+ mcps := m.ins.Config.MCP.Sorted()
maxMCPs = min(len(mcps), maxMCPs)
- return mcp.RenderMCPBlock(m.cfg, mcp.RenderOptions{
+ return mcp.RenderMCPBlock(m.ins.Config, mcp.RenderOptions{
MaxWidth: m.getMaxWidth(),
MaxItems: maxMCPs,
ShowSection: true,
@@ -544,15 +545,15 @@ func formatTokensAndCost(tokens, contextWindow int64, cost float64) string {
}
func (s *sidebarCmp) currentModelBlock() string {
- agentCfg := s.cfg.Agents["coder"]
+ agentCfg := s.ins.Config.Agents["coder"]
- selectedModel := s.cfg.Models[agentCfg.Model]
+ selectedModel := s.ins.Config.Models[agentCfg.Model]
- model := s.cfg.GetModelByType(agentCfg.Model)
+ model := s.ins.Config.GetModelByType(agentCfg.Model)
if model == nil {
return "No model found"
}
- modelProvider := s.cfg.GetProviderForModel(agentCfg.Model)
+ modelProvider := s.ins.Config.GetProviderForModel(agentCfg.Model)
t := styles.CurrentTheme()
@@ -13,6 +13,7 @@ import (
"github.com/charmbracelet/crush/internal/config"
"github.com/charmbracelet/crush/internal/home"
"github.com/charmbracelet/crush/internal/llm/prompt"
+ "github.com/charmbracelet/crush/internal/proto"
"github.com/charmbracelet/crush/internal/tui/components/chat"
"github.com/charmbracelet/crush/internal/tui/components/core"
"github.com/charmbracelet/crush/internal/tui/components/core/layout"
@@ -74,11 +75,11 @@ type splashCmp struct {
isAPIKeyValid bool
apiKeyValue string
- client *client.Client
- cfg *config.Config
+ c *client.Client
+ ins *proto.Instance
}
-func New(c *client.Client, cfg *config.Config) Splash {
+func New(c *client.Client, ins *proto.Instance) Splash {
keyMap := DefaultKeyMap()
listKeyMap := list.DefaultKeyMap()
listKeyMap.Down.SetEnabled(false)
@@ -90,12 +91,12 @@ func New(c *client.Client, cfg *config.Config) Splash {
listKeyMap.DownOneItem = keyMap.Next
listKeyMap.UpOneItem = keyMap.Previous
- modelList := models.NewModelListComponent(cfg, listKeyMap, "Find your fave", false)
+ modelList := models.NewModelListComponent(ins.Config, listKeyMap, "Find your fave", false)
apiKeyInput := models.NewAPIKeyInput()
return &splashCmp{
- client: c,
- cfg: cfg,
+ c: c,
+ ins: ins,
width: 0,
height: 0,
keyMap: keyMap,
@@ -217,7 +218,7 @@ func (s *splashCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
}),
func() tea.Msg {
start := time.Now()
- err := providerConfig.TestConnection(s.cfg.Resolver())
+ err := providerConfig.TestConnection(s.ins.Config.Resolver())
// intentionally wait for at least 750ms to make sure the user sees the spinner
elapsed := time.Since(start)
if elapsed < 750*time.Millisecond {
@@ -311,7 +312,7 @@ func (s *splashCmp) saveAPIKeyAndContinue(apiKey string) tea.Cmd {
return nil
}
- err := s.cfg.SetProviderAPIKey(string(s.selectedModel.Provider.ID), apiKey)
+ err := s.ins.Config.SetProviderAPIKey(string(s.selectedModel.Provider.ID), apiKey)
if err != nil {
return util.ReportError(fmt.Errorf("failed to save API key: %w", err))
}
@@ -329,7 +330,7 @@ func (s *splashCmp) saveAPIKeyAndContinue(apiKey string) tea.Cmd {
func (s *splashCmp) initializeProject() tea.Cmd {
s.needsProjectInit = false
- if err := config.MarkProjectInitialized(s.cfg); err != nil {
+ if err := config.MarkProjectInitialized(s.ins.Config); err != nil {
return util.ReportError(err)
}
var cmds []tea.Cmd
@@ -347,7 +348,7 @@ func (s *splashCmp) initializeProject() tea.Cmd {
}
func (s *splashCmp) setPreferredModel(selectedItem models.ModelOption) tea.Cmd {
- model := s.cfg.GetModel(string(selectedItem.Provider.ID), selectedItem.Model.ID)
+ model := s.ins.Config.GetModel(string(selectedItem.Provider.ID), selectedItem.Model.ID)
if model == nil {
return util.ReportError(fmt.Errorf("model %s not found for provider %s", selectedItem.Model.ID, selectedItem.Provider.ID))
}
@@ -359,7 +360,7 @@ func (s *splashCmp) setPreferredModel(selectedItem models.ModelOption) tea.Cmd {
MaxTokens: model.DefaultMaxTokens,
}
- err := s.cfg.UpdatePreferredModel(config.SelectedModelTypeLarge, selectedModel)
+ err := s.ins.Config.UpdatePreferredModel(config.SelectedModelTypeLarge, selectedModel)
if err != nil {
return util.ReportError(err)
}
@@ -371,16 +372,16 @@ func (s *splashCmp) setPreferredModel(selectedItem models.ModelOption) tea.Cmd {
}
if knownProvider == nil {
// for local provider we just use the same model
- err = s.cfg.UpdatePreferredModel(config.SelectedModelTypeSmall, selectedModel)
+ err = s.ins.Config.UpdatePreferredModel(config.SelectedModelTypeSmall, selectedModel)
if err != nil {
return util.ReportError(err)
}
} else {
smallModel := knownProvider.DefaultSmallModelID
- model := s.cfg.GetModel(string(selectedItem.Provider.ID), smallModel)
+ model := s.ins.Config.GetModel(string(selectedItem.Provider.ID), smallModel)
// should never happen
if model == nil {
- err = s.cfg.UpdatePreferredModel(config.SelectedModelTypeSmall, selectedModel)
+ err = s.ins.Config.UpdatePreferredModel(config.SelectedModelTypeSmall, selectedModel)
if err != nil {
return util.ReportError(err)
}
@@ -392,17 +393,17 @@ func (s *splashCmp) setPreferredModel(selectedItem models.ModelOption) tea.Cmd {
ReasoningEffort: model.DefaultReasoningEffort,
MaxTokens: model.DefaultMaxTokens,
}
- err = s.cfg.UpdatePreferredModel(config.SelectedModelTypeSmall, smallSelectedModel)
+ err = s.ins.Config.UpdatePreferredModel(config.SelectedModelTypeSmall, smallSelectedModel)
if err != nil {
return util.ReportError(err)
}
}
- s.cfg.SetupAgents()
+ s.ins.Config.SetupAgents()
return nil
}
func (s *splashCmp) getProvider(providerID catwalk.InferenceProvider) (*catwalk.Provider, error) {
- providers, err := config.Providers(s.cfg)
+ providers, err := config.Providers(s.ins.Config)
if err != nil {
return nil, err
}
@@ -415,7 +416,7 @@ func (s *splashCmp) getProvider(providerID catwalk.InferenceProvider) (*catwalk.
}
func (s *splashCmp) isProviderConfigured(providerID string) bool {
- if _, ok := s.cfg.Providers.Get(providerID); ok {
+ if _, ok := s.ins.Config.Providers.Get(providerID); ok {
return true
}
return false
@@ -652,11 +653,11 @@ func (s *splashCmp) cwdPart() string {
}
func (s *splashCmp) cwd() string {
- return home.Short(s.cfg.WorkingDir())
+ return home.Short(s.ins.Config.WorkingDir())
}
-func LSPList(c *client.Client, cfg *config.Config, maxWidth int) []string {
- return lspcomponent.RenderLSPList(c, cfg, lspcomponent.RenderOptions{
+func LSPList(c *client.Client, ins *proto.Instance, maxWidth int) []string {
+ return lspcomponent.RenderLSPList(c, ins, lspcomponent.RenderOptions{
MaxWidth: maxWidth,
ShowSection: false,
})
@@ -666,7 +667,7 @@ func (s *splashCmp) lspBlock() string {
t := styles.CurrentTheme()
maxWidth := s.getMaxInfoWidth() / 2
section := t.S().Subtle.Render("LSPs")
- lspList := append([]string{section, ""}, LSPList(s.client, s.cfg, maxWidth-1)...)
+ lspList := append([]string{section, ""}, LSPList(s.c, s.ins, maxWidth-1)...)
return t.S().Base.Width(maxWidth).PaddingRight(1).Render(
lipgloss.JoinVertical(
lipgloss.Left,
@@ -686,7 +687,7 @@ func (s *splashCmp) mcpBlock() string {
t := styles.CurrentTheme()
maxWidth := s.getMaxInfoWidth() / 2
section := t.S().Subtle.Render("MCPs")
- mcpList := append([]string{section, ""}, MCPList(s.cfg, maxWidth-1)...)
+ mcpList := append([]string{section, ""}, MCPList(s.ins.Config, maxWidth-1)...)
return t.S().Base.Width(maxWidth).PaddingRight(1).Render(
lipgloss.JoinVertical(
lipgloss.Left,
@@ -696,8 +697,8 @@ func (s *splashCmp) mcpBlock() string {
}
func (s *splashCmp) currentModelBlock() string {
- agentCfg := s.cfg.Agents["coder"]
- model := s.cfg.GetModelByType(agentCfg.Model)
+ agentCfg := s.ins.Config.Agents["coder"]
+ model := s.ins.Config.GetModelByType(agentCfg.Model)
if model == nil {
return ""
}
@@ -354,7 +354,7 @@ func (c *commandDialogCmp) defaultCommands() []Command {
if c.sessionID != "" {
agentCfg := c.cfg.Agents["coder"]
model := c.cfg.GetModelByType(agentCfg.Model)
- if model.SupportsImages {
+ if model != nil && model.SupportsImages {
commands = append(commands, Command{
ID: "file_picker",
Title: "Open File Picker",
@@ -9,6 +9,7 @@ import (
"github.com/charmbracelet/crush/internal/client"
"github.com/charmbracelet/crush/internal/llm/agent"
+ "github.com/charmbracelet/crush/internal/proto"
"github.com/charmbracelet/crush/internal/tui/components/core"
"github.com/charmbracelet/crush/internal/tui/components/dialogs"
"github.com/charmbracelet/crush/internal/tui/styles"
@@ -30,7 +31,8 @@ type compactDialogCmp struct {
sessionID string
state compactState
progress string
- client *client.Client
+ c *client.Client
+ ins *proto.Instance
noAsk bool // If true, skip confirmation dialog
}
@@ -43,13 +45,14 @@ const (
)
// NewCompactDialogCmp creates a new session compact dialog
-func NewCompactDialogCmp(c *client.Client, sessionID string, noAsk bool) CompactDialog {
+func NewCompactDialogCmp(c *client.Client, ins *proto.Instance, sessionID string, noAsk bool) CompactDialog {
return &compactDialogCmp{
sessionID: sessionID,
keyMap: DefaultKeyMap(),
state: stateConfirm,
selected: 0,
- client: c,
+ c: c,
+ ins: ins,
noAsk: noAsk,
}
}
@@ -134,7 +137,7 @@ func (c *compactDialogCmp) startCompaction() tea.Cmd {
c.state = stateCompacting
c.progress = "Starting summarization..."
return func() tea.Msg {
- err := c.client.AgentSummarizeSession(context.Background(), c.sessionID)
+ err := c.c.AgentSummarizeSession(context.Background(), c.ins.ID, c.sessionID)
if err != nil {
c.state = stateError
c.progress = "Error: " + err.Error()
@@ -7,8 +7,8 @@ import (
"strings"
"github.com/charmbracelet/crush/internal/client"
- "github.com/charmbracelet/crush/internal/config"
"github.com/charmbracelet/crush/internal/lsp"
+ "github.com/charmbracelet/crush/internal/proto"
"github.com/charmbracelet/crush/internal/tui/components/core"
"github.com/charmbracelet/crush/internal/tui/styles"
"github.com/charmbracelet/lipgloss/v2"
@@ -24,7 +24,7 @@ type RenderOptions struct {
}
// RenderLSPList renders a list of LSP status items with the given options.
-func RenderLSPList(c *client.Client, cfg *config.Config, opts RenderOptions) []string {
+func RenderLSPList(c *client.Client, ins *proto.Instance, opts RenderOptions) []string {
t := styles.CurrentTheme()
lspList := []string{}
@@ -37,14 +37,14 @@ func RenderLSPList(c *client.Client, cfg *config.Config, opts RenderOptions) []s
lspList = append(lspList, section, "")
}
- lspConfigs := cfg.LSP.Sorted()
+ lspConfigs := ins.Config.LSP.Sorted()
if len(lspConfigs) == 0 {
lspList = append(lspList, t.S().Base.Foreground(t.Border).Render("None"))
return lspList
}
// Get LSP states
- lspStates, err := c.GetLSPs(context.TODO())
+ lspStates, err := c.GetLSPs(context.TODO(), ins.ID)
if err != nil {
slog.Error("failed to get lsp clients")
return nil
@@ -98,7 +98,7 @@ func RenderLSPList(c *client.Client, cfg *config.Config, opts RenderOptions) []s
protocol.SeverityInformation: 0,
}
if _, ok := lspStates[l.Name]; ok {
- diags, err := c.GetLSPDiagnostics(context.TODO(), l.Name)
+ diags, err := c.GetLSPDiagnostics(context.TODO(), ins.ID, l.Name)
if err != nil {
slog.Error("couldn't get lsp diagnostics", "lsp", l.Name)
return nil
@@ -145,10 +145,10 @@ func RenderLSPList(c *client.Client, cfg *config.Config, opts RenderOptions) []s
}
// RenderLSPBlock renders a complete LSP block with optional truncation indicator.
-func RenderLSPBlock(c *client.Client, cfg *config.Config, opts RenderOptions, showTruncationIndicator bool) string {
+func RenderLSPBlock(c *client.Client, ins *proto.Instance, opts RenderOptions, showTruncationIndicator bool) string {
t := styles.CurrentTheme()
- lspList := RenderLSPList(c, cfg, opts)
- cfg, err := c.GetConfig(context.TODO())
+ lspList := RenderLSPList(c, ins, opts)
+ cfg, err := c.GetConfig(context.TODO(), ins.ID)
if err != nil {
slog.Error("failed to get config for lsp block rendering", "error", err)
return ""
@@ -15,6 +15,7 @@ import (
"github.com/charmbracelet/crush/internal/history"
"github.com/charmbracelet/crush/internal/message"
"github.com/charmbracelet/crush/internal/permission"
+ "github.com/charmbracelet/crush/internal/proto"
"github.com/charmbracelet/crush/internal/pubsub"
"github.com/charmbracelet/crush/internal/session"
"github.com/charmbracelet/crush/internal/tui/components/anim"
@@ -90,8 +91,8 @@ func cancelTimerCmd() tea.Cmd {
type chatPage struct {
width, height int
detailsWidth, detailsHeight int
- app *client.Client
- cfg *config.Config
+ c *client.Client
+ ins *proto.Instance
keyboardEnhancements tea.KeyboardEnhancementsMsg
// Layout state
@@ -118,33 +119,33 @@ type chatPage struct {
isProjectInit bool
}
-func New(app *client.Client, cfg *config.Config) ChatPage {
+func New(c *client.Client, ins *proto.Instance) ChatPage {
return &chatPage{
- app: app,
- cfg: cfg,
+ c: c,
+ ins: ins,
keyMap: DefaultKeyMap(),
- header: header.New(app, cfg),
- sidebar: sidebar.New(app, cfg, false),
- chat: chat.New(app, cfg),
- editor: editor.New(app),
- splash: splash.New(app, cfg),
+ header: header.New(c, ins),
+ sidebar: sidebar.New(c, ins, false),
+ chat: chat.New(c, ins),
+ editor: editor.New(c, ins),
+ splash: splash.New(c, ins),
focusedPane: PanelTypeSplash,
}
}
func (p *chatPage) Init() tea.Cmd {
- compact := p.cfg.Options.TUI.CompactMode
+ compact := p.ins.Config.Options.TUI.CompactMode
p.compact = compact
p.forceCompact = compact
p.sidebar.SetCompactMode(p.compact)
// Set splash state based on config
- if !config.HasInitialDataConfig(p.cfg) {
+ if !config.HasInitialDataConfig(p.ins.Config) {
// First-time setup: show model selection
p.splash.SetOnboarding(true)
p.isOnboarding = true
p.splashFullScreen = true
- } else if b, _ := config.ProjectNeedsInitialization(p.cfg); b {
+ } else if b, _ := config.ProjectNeedsInitialization(p.ins.Config); b {
// Project needs CRUSH.md initialization
p.splash.SetProjectInit(true)
p.isProjectInit = true
@@ -332,7 +333,7 @@ func (p *chatPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
return p, tea.Batch(cmds...)
case commands.CommandRunCustomMsg:
- info, err := p.app.GetAgentInfo(context.TODO())
+ info, err := p.c.GetAgentInfo(context.TODO(), p.ins.ID)
if err != nil {
return p, util.ReportError(fmt.Errorf("failed to get agent info: %w", err))
}
@@ -346,12 +347,12 @@ func (p *chatPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
}
case splash.OnboardingCompleteMsg:
p.splashFullScreen = false
- if b, _ := config.ProjectNeedsInitialization(p.cfg); b {
+ if b, _ := config.ProjectNeedsInitialization(p.ins.Config); b {
p.splash.SetProjectInit(true)
p.splashFullScreen = true
return p, p.SetSize(p.width, p.height)
}
- err := p.app.InitiateAgentProcessing(context.TODO())
+ err := p.c.InitiateAgentProcessing(context.TODO(), p.ins.ID)
if err != nil {
return p, util.ReportError(err)
}
@@ -360,7 +361,7 @@ func (p *chatPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
p.focusedPane = PanelTypeEditor
return p, p.SetSize(p.width, p.height)
case commands.NewSessionsMsg:
- info, err := p.app.GetAgentInfo(context.TODO())
+ info, err := p.c.GetAgentInfo(context.TODO(), p.ins.ID)
if err != nil {
return p, util.ReportError(fmt.Errorf("failed to get agent info: %w", err))
}
@@ -372,7 +373,7 @@ func (p *chatPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch {
case key.Matches(msg, p.keyMap.NewSession):
// if we have no agent do nothing
- info, err := p.app.GetAgentInfo(context.TODO())
+ info, err := p.c.GetAgentInfo(context.TODO(), p.ins.ID)
if err != nil || info.IsZero() {
return p, nil
}
@@ -381,8 +382,8 @@ func (p *chatPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
}
return p, p.newSession()
case key.Matches(msg, p.keyMap.AddAttachment):
- agentCfg := p.cfg.Agents["coder"]
- model := p.cfg.GetModelByType(agentCfg.Model)
+ agentCfg := p.ins.Config.Agents["coder"]
+ model := p.ins.Config.GetModelByType(agentCfg.Model)
if model.SupportsImages {
return p, util.CmdHandler(commands.OpenFilePickerMsg{})
} else {
@@ -397,7 +398,7 @@ func (p *chatPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
p.changeFocus()
return p, nil
case key.Matches(msg, p.keyMap.Cancel):
- info, err := p.app.GetAgentInfo(context.TODO())
+ info, err := p.c.GetAgentInfo(context.TODO(), p.ins.ID)
if err != nil {
return p, util.ReportError(fmt.Errorf("failed to get agent info: %w", err))
}
@@ -530,7 +531,7 @@ func (p *chatPage) View() string {
func (p *chatPage) updateCompactConfig(compact bool) tea.Cmd {
return func() tea.Msg {
- err := p.cfg.SetCompactMode(compact)
+ err := p.ins.Config.SetCompactMode(compact)
if err != nil {
return util.InfoMsg{
Type: util.InfoTypeError,
@@ -543,15 +544,15 @@ func (p *chatPage) updateCompactConfig(compact bool) tea.Cmd {
func (p *chatPage) toggleThinking() tea.Cmd {
return func() tea.Msg {
- agentCfg := p.cfg.Agents["coder"]
- currentModel := p.cfg.Models[agentCfg.Model]
+ agentCfg := p.ins.Config.Agents["coder"]
+ currentModel := p.ins.Config.Models[agentCfg.Model]
// Toggle the thinking mode
currentModel.Think = !currentModel.Think
- p.cfg.Models[agentCfg.Model] = currentModel
+ p.ins.Config.Models[agentCfg.Model] = currentModel
// Update the agent with the new configuration
- if err := p.app.UpdateAgent(context.TODO()); err != nil {
+ if err := p.c.UpdateAgent(context.TODO(), p.ins.ID); err != nil {
return util.InfoMsg{
Type: util.InfoTypeError,
Msg: "Failed to update thinking mode: " + err.Error(),
@@ -571,15 +572,15 @@ func (p *chatPage) toggleThinking() tea.Cmd {
func (p *chatPage) openReasoningDialog() tea.Cmd {
return func() tea.Msg {
- agentCfg := p.cfg.Agents["coder"]
- model := p.cfg.GetModelByType(agentCfg.Model)
- providerCfg := p.cfg.GetProviderForModel(agentCfg.Model)
+ agentCfg := p.ins.Config.Agents["coder"]
+ model := p.ins.Config.GetModelByType(agentCfg.Model)
+ providerCfg := p.ins.Config.GetProviderForModel(agentCfg.Model)
if providerCfg != nil && model != nil &&
providerCfg.Type == catwalk.TypeOpenAI && model.HasReasoningEffort {
// Return the OpenDialogMsg directly so it bubbles up to the main TUI
return dialogs.OpenDialogMsg{
- Model: reasoning.NewReasoningDialog(p.cfg),
+ Model: reasoning.NewReasoningDialog(p.ins.Config),
}
}
return nil
@@ -588,7 +589,7 @@ func (p *chatPage) openReasoningDialog() tea.Cmd {
func (p *chatPage) handleReasoningEffortSelected(effort string) tea.Cmd {
return func() tea.Msg {
- cfg := p.cfg
+ cfg := p.ins.Config
agentCfg := cfg.Agents["coder"]
currentModel := cfg.Models[agentCfg.Model]
@@ -597,7 +598,7 @@ func (p *chatPage) handleReasoningEffortSelected(effort string) tea.Cmd {
cfg.Models[agentCfg.Model] = currentModel
// Update the agent with the new configuration
- if err := p.app.UpdateAgent(context.TODO()); err != nil {
+ if err := p.c.UpdateAgent(context.TODO(), p.ins.ID); err != nil {
return util.InfoMsg{
Type: util.InfoTypeError,
Msg: "Failed to update reasoning effort: " + err.Error(),
@@ -718,13 +719,13 @@ func (p *chatPage) changeFocus() {
func (p *chatPage) cancel() tea.Cmd {
if p.isCanceling {
p.isCanceling = false
- _ = p.app.ClearAgentSessionQueuedPrompts(context.TODO(), p.session.ID)
+ _ = p.c.ClearAgentSessionQueuedPrompts(context.TODO(), p.ins.ID, p.session.ID)
return nil
}
- queued, _ := p.app.GetAgentSessionQueuedPrompts(context.TODO(), p.session.ID)
+ queued, _ := p.c.GetAgentSessionQueuedPrompts(context.TODO(), p.ins.ID, p.session.ID)
if queued > 0 {
- _ = p.app.ClearAgentSessionQueuedPrompts(context.TODO(), p.session.ID)
+ _ = p.c.ClearAgentSessionQueuedPrompts(context.TODO(), p.ins.ID, p.session.ID)
return nil
}
p.isCanceling = true
@@ -750,18 +751,18 @@ func (p *chatPage) sendMessage(text string, attachments []message.Attachment) te
session := p.session
var cmds []tea.Cmd
if p.session.ID == "" {
- newSession, err := p.app.CreateSession(context.Background(), "New Session")
+ newSession, err := p.c.CreateSession(context.Background(), p.ins.ID, "New Session")
if err != nil {
return util.ReportError(err)
}
session = *newSession
cmds = append(cmds, util.CmdHandler(chat.SessionSelectedMsg(session)))
}
- info, err := p.app.GetAgentInfo(context.TODO())
+ info, err := p.c.GetAgentInfo(context.TODO(), p.ins.ID)
if err != nil || info.IsZero() {
return util.ReportError(fmt.Errorf("coder agent is not initialized"))
}
- if err := p.app.SendMessage(context.Background(), session.ID, text, attachments...); err != nil {
+ if err := p.c.SendMessage(context.Background(), p.ins.ID, session.ID, text, attachments...); err != nil {
return util.ReportError(err)
}
cmds = append(cmds, p.chat.GoToBottom())
@@ -773,7 +774,7 @@ func (p *chatPage) Bindings() []key.Binding {
p.keyMap.NewSession,
p.keyMap.AddAttachment,
}
- info, err := p.app.GetAgentInfo(context.TODO())
+ info, err := p.c.GetAgentInfo(context.TODO(), p.ins.ID)
if err == nil && info.IsBusy {
cancelBinding := p.keyMap.Cancel
if p.isCanceling {
@@ -895,7 +896,7 @@ func (p *chatPage) Help() help.KeyMap {
}
return core.NewSimpleHelp(shortList, fullList)
}
- info, err := p.app.GetAgentInfo(context.TODO())
+ info, err := p.c.GetAgentInfo(context.TODO(), p.ins.ID)
if err == nil && info.IsBusy {
cancelBinding := key.NewBinding(
key.WithKeys("esc", "alt+esc"),
@@ -907,7 +908,7 @@ func (p *chatPage) Help() help.KeyMap {
key.WithHelp("esc", "press again to cancel"),
)
}
- queued, _ := p.app.GetAgentSessionQueuedPrompts(context.TODO(), p.session.ID)
+ queued, _ := p.c.GetAgentSessionQueuedPrompts(context.TODO(), p.ins.ID, p.session.ID)
if queued > 0 {
cancelBinding = key.NewBinding(
key.WithKeys("esc", "alt+esc"),
@@ -68,8 +68,8 @@ type appModel struct {
status status.StatusCmp
showingFullHelp bool
- app *client.Client
- cfg *config.Config
+ c *client.Client
+ ins *proto.Instance
dialog dialogs.DialogCmp
completions completions.Completions
@@ -103,7 +103,7 @@ func (a appModel) Init() tea.Cmd {
func (a *appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
var cmds []tea.Cmd
var cmd tea.Cmd
- a.isConfigured = config.HasInitialDataConfig(a.cfg)
+ a.isConfigured = config.HasInitialDataConfig(a.ins.Config)
switch msg := msg.(type) {
case tea.KeyboardEnhancementsMsg:
@@ -169,7 +169,7 @@ func (a *appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
// Commands
case commands.SwitchSessionsMsg:
return a, func() tea.Msg {
- allSessions, _ := a.app.ListSessions(context.Background())
+ allSessions, _ := a.c.ListSessions(context.Background(), a.ins.ID)
return dialogs.OpenDialogMsg{
Model: sessions.NewSessionDialogCmp(allSessions, a.selectedSessionID),
}
@@ -178,24 +178,24 @@ func (a *appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
case commands.SwitchModelMsg:
return a, util.CmdHandler(
dialogs.OpenDialogMsg{
- Model: models.NewModelDialogCmp(a.cfg),
+ Model: models.NewModelDialogCmp(a.ins.Config),
},
)
// Compact
case commands.CompactMsg:
return a, util.CmdHandler(dialogs.OpenDialogMsg{
- Model: compact.NewCompactDialogCmp(a.app, msg.SessionID, true),
+ Model: compact.NewCompactDialogCmp(a.c, a.ins, msg.SessionID, true),
})
case commands.QuitMsg:
return a, util.CmdHandler(dialogs.OpenDialogMsg{
Model: quit.NewQuitDialog(),
})
case commands.ToggleYoloModeMsg:
- skip, err := a.app.GetPermissionsSkipRequests(context.TODO())
+ skip, err := a.c.GetPermissionsSkipRequests(context.TODO(), a.ins.ID)
if err != nil {
return a, util.ReportError(fmt.Errorf("failed to get permissions skip requests: %v", err))
}
- if err := a.app.SetPermissionsSkipRequests(context.TODO(), !skip); err != nil {
+ if err := a.c.SetPermissionsSkipRequests(context.TODO(), a.ins.ID, !skip); err != nil {
return a, util.ReportError(fmt.Errorf("failed to toggle YOLO mode: %v", err))
}
case commands.ToggleHelpMsg:
@@ -204,17 +204,17 @@ func (a *appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
return a, a.handleWindowResize(a.wWidth, a.wHeight)
// Model Switch
case models.ModelSelectedMsg:
- info, err := a.app.GetAgentInfo(context.TODO())
+ info, err := a.c.GetAgentInfo(context.TODO(), a.ins.ID)
if err != nil {
return a, util.ReportError(fmt.Errorf("failed to check if agent is busy: %v", err))
}
if info.IsBusy {
return a, util.ReportWarn("Agent is busy, please wait...")
}
- a.cfg.UpdatePreferredModel(msg.ModelType, msg.Model)
+ a.ins.Config.UpdatePreferredModel(msg.ModelType, msg.Model)
// Update the agent with the new model/provider configuration
- if err := a.app.UpdateAgent(context.TODO()); err != nil {
+ if err := a.c.UpdateAgent(context.TODO(), a.ins.ID); err != nil {
return a, util.ReportError(fmt.Errorf("model changed to %s but failed to update agent: %v", msg.Model.Model, err))
}
@@ -233,7 +233,7 @@ func (a *appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
return a, util.CmdHandler(dialogs.CloseDialogMsg{})
}
return a, util.CmdHandler(dialogs.OpenDialogMsg{
- Model: filepicker.NewFilePickerCmp(a.cfg.WorkingDir()),
+ Model: filepicker.NewFilePickerCmp(a.ins.Config.WorkingDir()),
})
// Permissions
case pubsub.Event[permission.PermissionNotification]:
@@ -252,11 +252,11 @@ func (a *appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
case pubsub.Event[permission.PermissionRequest]:
return a, util.CmdHandler(dialogs.OpenDialogMsg{
Model: permissions.NewPermissionDialogCmp(msg.Payload, &permissions.Options{
- DiffMode: a.cfg.Options.TUI.DiffMode,
+ DiffMode: a.ins.Config.Options.TUI.DiffMode,
}),
})
case permissions.PermissionResponseMsg:
- if err := a.app.GrantPermission(context.TODO(), proto.PermissionGrant(msg)); err != nil {
+ if err := a.c.GrantPermission(context.TODO(), a.ins.ID, proto.PermissionGrant(msg)); err != nil {
return a, util.ReportError(fmt.Errorf("failed to grant permission: %v", err))
}
return a, nil
@@ -281,18 +281,18 @@ func (a *appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
// Handle auto-compact logic
if payload.Done && payload.Type == agent.AgentEventTypeResponse && a.selectedSessionID != "" {
// Get current session to check token usage
- session, err := a.app.GetSession(context.Background(), a.selectedSessionID)
+ session, err := a.c.GetSession(context.Background(), a.ins.ID, a.selectedSessionID)
if err == nil {
- info, err := a.app.GetAgentInfo(context.Background())
+ info, err := a.c.GetAgentInfo(context.Background(), a.ins.ID)
if err != nil {
return a, util.ReportError(fmt.Errorf("failed to check if agent is busy: %v", err))
}
model := info.Model
contextWindow := model.ContextWindow
tokens := session.CompletionTokens + session.PromptTokens
- if (tokens >= int64(float64(contextWindow)*0.95)) && !a.cfg.Options.DisableAutoSummarize { // Show compact confirmation dialog
+ if (tokens >= int64(float64(contextWindow)*0.95)) && !a.ins.Config.Options.DisableAutoSummarize { // Show compact confirmation dialog
cmds = append(cmds, util.CmdHandler(dialogs.OpenDialogMsg{
- Model: compact.NewCompactDialogCmp(a.app, a.selectedSessionID, false),
+ Model: compact.NewCompactDialogCmp(a.c, a.ins, a.selectedSessionID, false),
}))
}
}
@@ -305,7 +305,7 @@ func (a *appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
return a, nil
}
- a.isConfigured = config.HasInitialDataConfig(a.cfg)
+ a.isConfigured = config.HasInitialDataConfig(a.ins.Config)
updated, pageCmd := item.Update(msg)
if model, ok := updated.(util.Model); ok {
a.pages[a.currentPage] = model
@@ -472,7 +472,7 @@ func (a *appModel) handleKeyPressMsg(msg tea.KeyPressMsg) tea.Cmd {
return nil
}
return util.CmdHandler(dialogs.OpenDialogMsg{
- Model: commands.NewCommandDialog(a.cfg, a.selectedSessionID),
+ Model: commands.NewCommandDialog(a.ins.Config, a.selectedSessionID),
})
case key.Matches(msg, a.keyMap.Sessions):
// if the app is not configured show no sessions
@@ -492,7 +492,7 @@ func (a *appModel) handleKeyPressMsg(msg tea.KeyPressMsg) tea.Cmd {
}
cmds = append(cmds,
func() tea.Msg {
- allSessions, _ := a.app.ListSessions(context.Background())
+ allSessions, _ := a.c.ListSessions(context.Background(), a.ins.ID)
return dialogs.OpenDialogMsg{
Model: sessions.NewSessionDialogCmp(allSessions, a.selectedSessionID),
}
@@ -500,7 +500,7 @@ func (a *appModel) handleKeyPressMsg(msg tea.KeyPressMsg) tea.Cmd {
)
return tea.Sequence(cmds...)
case key.Matches(msg, a.keyMap.Suspend):
- info, err := a.app.GetAgentInfo(context.TODO())
+ info, err := a.c.GetAgentInfo(context.TODO(), a.ins.ID)
if err != nil || info.IsBusy {
return util.ReportWarn("Agent is busy, please wait...")
}
@@ -521,7 +521,7 @@ func (a *appModel) handleKeyPressMsg(msg tea.KeyPressMsg) tea.Cmd {
// moveToPage handles navigation between different pages in the application.
func (a *appModel) moveToPage(pageID page.PageID) tea.Cmd {
- info, err := a.app.GetAgentInfo(context.TODO())
+ info, err := a.c.GetAgentInfo(context.TODO(), a.ins.ID)
if err != nil {
return util.ReportError(fmt.Errorf("failed to check if agent is busy: %v", err))
}
@@ -626,25 +626,20 @@ func (a *appModel) View() tea.View {
}
// New creates and initializes a new TUI application model.
-func New(app *client.Client) (tea.Model, error) {
- cfg, err := app.GetConfig(context.TODO())
- if err != nil {
- return nil, fmt.Errorf("failed to get config: %v", err)
- }
-
+func New(c *client.Client, ins *proto.Instance) (tea.Model, error) {
// Setup logs
log.Setup(
- filepath.Join(cfg.Options.DataDirectory, "logs", "tui.log"),
- cfg.Options.Debug,
+ filepath.Join(ins.Config.Options.DataDirectory, "logs", "tui.log"),
+ ins.Config.Options.Debug,
)
- chatPage := chat.New(app, cfg)
+ chatPage := chat.New(c, ins)
keyMap := DefaultKeyMap()
keyMap.pageBindings = chatPage.Bindings()
model := &appModel{
+ ins: ins,
currentPage: chat.ChatPageID,
- app: app,
- cfg: cfg,
+ c: c,
status: status.NewStatusCmp(),
loadedPages: make(map[page.PageID]bool),
keyMap: keyMap,