@@ -11,6 +11,9 @@ import (
11 "github.com/kujtimiihoxha/opencode/internal/llm/models"
11 "github.com/kujtimiihoxha/opencode/internal/llm/models"
12 "github.com/kujtimiihoxha/opencode/internal/lsp"
12 "github.com/kujtimiihoxha/opencode/internal/lsp"
13 "github.com/kujtimiihoxha/opencode/internal/lsp/protocol"
13 "github.com/kujtimiihoxha/opencode/internal/lsp/protocol"
14+ "github.com/kujtimiihoxha/opencode/internal/pubsub"
15+ "github.com/kujtimiihoxha/opencode/internal/session"
16+ "github.com/kujtimiihoxha/opencode/internal/tui/components/chat"
14 "github.com/kujtimiihoxha/opencode/internal/tui/styles"
17 "github.com/kujtimiihoxha/opencode/internal/tui/styles"
15 "github.com/kujtimiihoxha/opencode/internal/tui/util"
18 "github.com/kujtimiihoxha/opencode/internal/tui/util"
@@ -20,6 +23,7 @@ type statusCmp struct {
21 messageTTL time.Duration
24 messageTTL time.Duration
22 lspClients map[string]*lsp.Client
25 lspClients map[string]*lsp.Client
26+ session session.Session
25// clearMessageCmd is a command that clears status messages after a timeout
29// clearMessageCmd is a command that clears status messages after a timeout
@@ -38,6 +42,16 @@ func (m statusCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
38 case tea.WindowSizeMsg:
42 case tea.WindowSizeMsg:
39 m.width = msg.Width
43 m.width = msg.Width
40 return m, nil
44 return m, nil
45+ case chat.SessionSelectedMsg:
47+ case chat.SessionClearedMsg:
48+ m.session = session.Session{}
49+ case pubsub.Event[session.Session]:
50+ if msg.Type == pubsub.UpdatedEvent {
51+ if m.session.ID == msg.Payload.ID {
52+ m.session = msg.Payload
41 case util.InfoMsg:
55 case util.InfoMsg:
42 m.info = msg
56 m.info = msg
43 ttl := msg.TTL
57 ttl := msg.TTL
@@ -53,8 +67,43 @@ func (m statusCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
54var helpWidget = styles.Padded.Background(styles.ForgroundMid).Foreground(styles.BackgroundDarker).Bold(true).Render("ctrl+? help")
68var helpWidget = styles.Padded.Background(styles.ForgroundMid).Foreground(styles.BackgroundDarker).Bold(true).Render("ctrl+? help")
70+func formatTokensAndCost(tokens int64, cost float64) string {
71+ // Format tokens in human-readable format (e.g., 110K, 1.2M)
72+ var formattedTokens string
74+ case tokens >= 1_000_000:
75+ formattedTokens = fmt.Sprintf("%.1fM", float64(tokens)/1_000_000)
76+ case tokens >= 1_000:
77+ formattedTokens = fmt.Sprintf("%.1fK", float64(tokens)/1_000)
79+ formattedTokens = fmt.Sprintf("%d", tokens)
82+ // Remove .0 suffix if present
83+ if strings.HasSuffix(formattedTokens, ".0K") {
84+ formattedTokens = strings.Replace(formattedTokens, ".0K", "K", 1)
86+ if strings.HasSuffix(formattedTokens, ".0M") {
87+ formattedTokens = strings.Replace(formattedTokens, ".0M", "M", 1)
90+ // Format cost with $ symbol and 2 decimal places
91+ formattedCost := fmt.Sprintf("$%.2f", cost)
93+ return fmt.Sprintf("Tokens: %s, Cost: %s", formattedTokens, formattedCost)
56func (m statusCmp) View() string {
96func (m statusCmp) View() string {
57 status := helpWidget
97 status := helpWidget
98+ if m.session.ID != "" {
99+ tokens := formatTokensAndCost(m.session.PromptTokens+m.session.CompletionTokens, m.session.Cost)
100+ tokensStyle := styles.Padded.
101+ Background(styles.Forground).
102+ Foreground(styles.BackgroundDim).
104+ status += tokensStyle
58 diagnostics := styles.Padded.Background(styles.BackgroundDarker).Render(m.projectDiagnostics())
107 diagnostics := styles.Padded.Background(styles.BackgroundDarker).Render(m.projectDiagnostics())
59 if m.info.Msg != "" {
108 if m.info.Msg != "" {
60 infoStyle := styles.Padded.
109 infoStyle := styles.Padded.
@@ -82,6 +131,7 @@ func (m statusCmp) View() string {
82 Width(m.availableFooterMsgWidth(diagnostics)).
131 Width(m.availableFooterMsgWidth(diagnostics)).
83 Render("")
132 Render("")
85 status += diagnostics
135 status += diagnostics
86 status += m.model()
136 status += m.model()
87 return status
137 return status
@@ -136,7 +186,11 @@ func (m *statusCmp) projectDiagnostics() string {
138func (m statusCmp) availableFooterMsgWidth(diagnostics string) int {
188func (m statusCmp) availableFooterMsgWidth(diagnostics string) int {
190+ if m.session.ID != "" {
191+ tokens = formatTokensAndCost(m.session.PromptTokens+m.session.CompletionTokens, m.session.Cost)
139− return max(0, m.width-lipgloss.Width(helpWidget)-lipgloss.Width(m.model())-lipgloss.Width(diagnostics))
193+ return max(0, m.width-lipgloss.Width(helpWidget)-lipgloss.Width(m.model())-lipgloss.Width(diagnostics)-lipgloss.Width(tokens))
142func (m statusCmp) model() string {
196func (m statusCmp) model() string {