Files
Ed Zynda aeb704367c feat(app): update token counts and context fill after every step
- Set context tokens per-step in recordStepUsage instead of waiting
  for turn completion; each step re-sends the full conversation so
  the reported usage monotonically increases
- Add UsageUpdatedEvent to trigger a TUI re-render after each step
  so the status bar reflects updated tokens, cost, and context %
  even during gaps between streaming chunks
- Update test to expect per-step context token updates
2026-04-23 12:56:00 +03:00

355 lines
14 KiB
Go

package app
import kit "github.com/mark3labs/kit/pkg/kit"
// StreamChunkEvent is sent by the app layer when a streaming text delta arrives
// from the LLM. Each chunk contains an incremental portion of the response.
type StreamChunkEvent struct {
// Content is the incremental text delta from the streaming response.
Content string
}
// ReasoningChunkEvent is sent when a streaming reasoning/thinking delta arrives
// from the LLM. Thinking content is rendered separately from regular text.
type ReasoningChunkEvent struct {
// Delta is the incremental reasoning text from the streaming response.
Delta string
}
// ReasoningCompleteEvent is sent when reasoning/thinking is finished, after
// the last reasoning token has been processed. The TUI uses this to freeze
// the reasoning duration counter.
type ReasoningCompleteEvent struct{}
// ToolCallStartedEvent is sent when a tool call has been parsed and is about to execute.
// It carries the tool name and its arguments for display purposes.
type ToolCallStartedEvent struct {
// ToolCallID is the stable identifier for correlating tool lifecycle events.
ToolCallID string
// ToolName is the name of the tool being called.
ToolName string
// ToolArgs is the JSON-encoded arguments for the tool call.
ToolArgs string
}
// ToolCallInputStartEvent is sent when the LLM begins generating tool call
// arguments. The tool name is known but the full argument JSON is still being
// streamed. UIs can use this to show a "running" indicator immediately instead
// of waiting for the full argument JSON to finish streaming.
type ToolCallInputStartEvent struct {
// ToolCallID is the stable identifier for correlating tool lifecycle events.
ToolCallID string
// ToolName is the name of the tool being called.
ToolName string
// ToolKind classifies the tool: "execute", "edit", "read", "search", "agent".
ToolKind string
}
// ToolCallInputDeltaEvent is sent for each streamed fragment of tool call
// arguments as they arrive from the LLM. Useful for live-previewing content
// or showing a progress indicator with byte count.
type ToolCallInputDeltaEvent struct {
// ToolCallID is the stable identifier for correlating tool lifecycle events.
ToolCallID string
// Delta is a JSON fragment of tool call arguments.
Delta string
}
// ToolCallInputEndEvent is sent when tool argument streaming is complete,
// before the tool call is parsed and execution begins.
type ToolCallInputEndEvent struct {
// ToolCallID is the stable identifier for correlating tool lifecycle events.
ToolCallID string
}
// ToolExecutionEvent is sent when a tool starts or finishes executing.
// The IsStarting flag distinguishes between the start and end of execution.
type ToolExecutionEvent struct {
// ToolCallID is the stable identifier for correlating tool lifecycle events.
ToolCallID string
// ToolName is the name of the tool being executed.
ToolName string
// ToolArgs is the JSON-encoded arguments for the tool call (only set when IsStarting is true).
ToolArgs string
// IsStarting is true when execution is beginning, false when it is complete.
IsStarting bool
}
// ToolResultEvent is sent after a tool execution completes with its result.
type ToolResultEvent struct {
// ToolCallID is the stable identifier for correlating tool lifecycle events.
ToolCallID string
// ToolName is the name of the tool that was executed.
ToolName string
// ToolArgs is the JSON-encoded arguments that were passed to the tool.
ToolArgs string
// Result is the text output from the tool execution.
Result string
// IsError indicates whether the tool returned an error result.
IsError bool
}
// ToolOutputEvent is sent when a tool produces streaming output chunks (e.g., bash output).
// This allows the TUI to display tool output as it arrives, before the tool completes.
type ToolOutputEvent struct {
// ToolCallID is the stable identifier for the tool call producing output.
ToolCallID string
// ToolName is the name of the tool producing output.
ToolName string
// Chunk is a piece of the tool's output text.
Chunk string
// IsStderr indicates whether this chunk came from stderr.
IsStderr bool
}
// ToolCallContentEvent is sent when a step includes text content alongside tool calls.
// This allows the TUI to display assistant commentary that accompanies tool usage.
type ToolCallContentEvent struct {
// Content is the assistant text that accompanies one or more tool calls.
Content string
}
// PasswordPromptEvent is sent when a sudo command needs a password.
// The TUI should display a password prompt overlay and send the result back.
type PasswordPromptEvent struct {
// Prompt is the message to display to the user.
Prompt string
// ResponseCh receives the password from the TUI.
// The TUI must send exactly one value.
ResponseCh chan<- PasswordPromptResponse
}
// PasswordPromptResponse carries the user's password input.
type PasswordPromptResponse struct {
// Password is the entered password.
Password string
// Cancelled is true if the user cancelled the prompt.
Cancelled bool
}
// ResponseCompleteEvent is sent when the LLM produces a final (non-streaming) response.
// In streaming mode, this may be empty if all content was delivered via StreamChunkEvents.
type ResponseCompleteEvent struct {
// Content is the complete final response text.
Content string
}
// StepCompleteEvent is sent when an agent step finishes successfully.
type StepCompleteEvent struct {
// ResponseText is the final assistant response text.
ResponseText string
}
// StepErrorEvent is sent when an agent step fails with an error.
// The TUI should display the error inline and transition back to input state.
type StepErrorEvent struct {
// Err is the error that caused the step to fail.
Err error
}
// StepCancelledEvent is sent when an agent step is cancelled by the user
// (e.g. via double-ESC). The TUI should flush any partially streamed content,
// cut off the agent message where it was, and return to input state without
// displaying an error.
type StepCancelledEvent struct{}
// QueueUpdatedEvent is sent whenever the message queue length changes.
// The TUI uses this to update the queue badge display.
type QueueUpdatedEvent struct {
// Length is the current number of messages waiting in the queue.
Length int
}
// SpinnerEvent is sent to show or hide the spinner animation in the stream component.
// The spinner is shown before the first streaming chunk arrives and hidden once
// content begins flowing or the step completes.
type SpinnerEvent struct {
// Show is true to display the spinner and false to hide it.
Show bool
}
// MessageCreatedEvent is sent when a new message is added to the message store.
// This allows the TUI to stay in sync with the conversation history.
type MessageCreatedEvent struct {
// Message is the message that was added to the store.
Message kit.LLMMessage
}
// CompactCompleteEvent is sent when a /compact operation finishes successfully.
// It carries the summary text and before/after statistics.
type CompactCompleteEvent struct {
// Summary is the LLM-generated structured summary of the compacted messages.
Summary string
// OriginalTokens is the estimated token count before compaction.
OriginalTokens int
// CompactedTokens is the estimated token count after compaction.
CompactedTokens int
// MessagesRemoved is the number of messages that were summarised away.
MessagesRemoved int
}
// CompactErrorEvent is sent when a /compact operation fails.
type CompactErrorEvent struct {
// Err is the error that caused compaction to fail.
Err error
}
// SteerConsumedEvent is sent when one or more steering messages have been
// consumed — either injected mid-turn via PrepareStep, or drained into the
// queue after a turn completes. The TUI uses this to clear the steering
// badge from the display.
type SteerConsumedEvent struct{}
// ModelChangedEvent is sent when an extension changes the active model via
// ctx.SetModel. The TUI updates the model name shown in the status bar and
// message attribution.
type ModelChangedEvent struct {
// ProviderName is the new provider (e.g. "anthropic").
ProviderName string
// ModelName is the new model ID (e.g. "claude-3-5-haiku-20241022").
ModelName string
}
// UsageUpdatedEvent is sent after each completed LLM step to notify the TUI
// that token counts and costs have changed. The UsageTracker is updated
// in-place before this event is sent; the TUI just needs to re-render to
// reflect the new values in the status bar.
type UsageUpdatedEvent struct{}
// WidgetUpdateEvent is sent when an extension adds, updates, or removes a
// widget via ctx.SetWidget or ctx.RemoveWidget. The TUI re-reads widget state
// from its WidgetProvider on the next render cycle.
type WidgetUpdateEvent struct{}
// ContentReloadEvent is sent when prompt templates or skills are reloaded
// from disk (e.g. by a file watcher detecting changes). The TUI refreshes
// its autocomplete entries and internal state from the provider callbacks.
type ContentReloadEvent struct{}
// MCPToolsReadyEvent is sent when background MCP tool loading completes.
// The TUI refreshes its tool names and MCP tool count from provider callbacks
// so that /tools and the startup info bar reflect the loaded MCP tools.
type MCPToolsReadyEvent struct{}
// MCPServerLoadedEvent is sent when a single MCP server finishes loading
// (successfully or with error). The TUI displays a system message so users
// see real-time progress as each server initializes.
type MCPServerLoadedEvent struct {
ServerName string
ToolCount int
Error error // nil on success
}
// EditorTextSetEvent is sent when an extension calls ctx.SetEditorText to
// pre-fill the input editor with text. The TUI handles this by setting the
// textarea content and moving the cursor to the end.
type EditorTextSetEvent struct {
Text string
}
// ExtensionPrintEvent is sent when an extension calls ctx.Print, ctx.PrintInfo,
// ctx.PrintError, or ctx.PrintBlock. The TUI renders it via the appropriate
// renderer and tea.Println (scrollback); the CLI handler uses
// DisplayInfo/DisplayError or plain fmt.Println. This exists because BubbleTea
// captures stdout, so plain fmt.Println inside extensions would be swallowed.
type ExtensionPrintEvent struct {
// Text is the content the extension wants to display to the user.
Text string
// Level controls the rendering style:
// "" — plain text (no styling)
// "info" — system message block (bordered, themed)
// "error" — error block (red border, bold text)
// "block" — custom block with BorderColor and Subtitle
Level string
// BorderColor is a hex color (e.g. "#a6e3a1") for Level="block".
BorderColor string
// Subtitle is optional muted text below the content for Level="block".
Subtitle string
}
// PromptResponse carries the user's answer to an interactive prompt. The TUI
// sends exactly one PromptResponse through the channel embedded in
// PromptRequestEvent when the user completes or cancels the prompt.
type PromptResponse struct {
// Value is the response text — the selected option (select), or the
// entered text (input). Unused for confirm prompts.
Value string
// Index is the zero-based index of the selected option (select only).
Index int
// Confirmed is the boolean answer for confirm prompts.
Confirmed bool
// Cancelled is true if the user dismissed the prompt (ESC) or the
// prompt could not be shown (e.g. app shutting down).
Cancelled bool
}
// PromptRequestEvent is sent when an extension requests an interactive
// prompt from the user (select, confirm, or text input). The TUI enters a
// modal prompt state, renders the prompt, and sends a single PromptResponse
// through ResponseCh when the user completes or cancels.
//
// The extension goroutine blocks on the read side of ResponseCh until the
// TUI sends a response. The channel must have buffer size >= 1.
type PromptRequestEvent struct {
// PromptType is "select", "confirm", or "input".
PromptType string
// Message is the question displayed to the user.
Message string
// Options lists the choices for select prompts.
Options []string
// Default is the pre-filled value: "true"/"false" for confirm prompts,
// or the initial text for input prompts.
Default string
// Placeholder is the ghost text for input prompts.
Placeholder string
// ResponseCh receives the user's answer. The TUI must send exactly one
// value. The channel must be buffered (cap >= 1) so sending never
// blocks inside Update().
ResponseCh chan<- PromptResponse
}
// OverlayResponse carries the user's answer to a modal overlay dialog. The
// TUI sends exactly one OverlayResponse through the channel embedded in
// OverlayRequestEvent when the user completes or cancels the overlay.
type OverlayResponse struct {
// Action is the text of the selected action button, or "" if no actions
// were configured or the dialog was dismissed without selection.
Action string
// Index is the zero-based index of the selected action, or -1 if no
// action was selected.
Index int
// Cancelled is true if the user dismissed the overlay (ESC) or the
// overlay could not be shown (e.g. non-interactive mode).
Cancelled bool
}
// OverlayRequestEvent is sent when an extension requests a modal overlay
// dialog. The TUI enters an overlay state, renders the dialog, and sends a
// single OverlayResponse through ResponseCh when the user dismisses or
// selects an action.
//
// The extension goroutine blocks on the read side of ResponseCh until the
// TUI sends a response. The channel must have buffer size >= 1.
type OverlayRequestEvent struct {
// Title is displayed at the top of the dialog. Empty means no title.
Title string
// Content is the text to render inside the dialog body.
Content string
// Markdown, when true, renders Content as styled markdown.
Markdown bool
// BorderColor is a hex color for the dialog border. Empty uses default.
BorderColor string
// Background is a hex color for the dialog background. Empty = none.
Background string
// Width is the dialog width in columns. 0 = auto (60% of terminal).
Width int
// MaxHeight limits dialog height. 0 = auto (80% of terminal).
MaxHeight int
// Anchor is the vertical positioning: "center", "top-center", "bottom-center".
Anchor string
// Actions lists the action button labels. Empty = simple dismiss dialog.
Actions []string
// ResponseCh receives the user's response. Must have buffer size >= 1.
ResponseCh chan<- OverlayResponse
}