| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865 |
- package messages
- import (
- "encoding/json"
- "fmt"
- "path/filepath"
- "strings"
- "time"
- "github.com/atotto/clipboard"
- "github.com/charmbracelet/bubbles/v2/key"
- tea "github.com/charmbracelet/bubbletea/v2"
- "github.com/charmbracelet/crush/internal/agent"
- "github.com/charmbracelet/crush/internal/agent/tools"
- "github.com/charmbracelet/crush/internal/diff"
- "github.com/charmbracelet/crush/internal/fsext"
- "github.com/charmbracelet/crush/internal/message"
- "github.com/charmbracelet/crush/internal/permission"
- "github.com/charmbracelet/crush/internal/tui/components/anim"
- "github.com/charmbracelet/crush/internal/tui/components/core/layout"
- "github.com/charmbracelet/crush/internal/tui/styles"
- "github.com/charmbracelet/crush/internal/tui/util"
- "github.com/charmbracelet/lipgloss/v2"
- "github.com/charmbracelet/x/ansi"
- )
- // ToolCallCmp defines the interface for tool call components in the chat interface.
- // It manages the display of tool execution including pending states, results, and errors.
- type ToolCallCmp interface {
- util.Model // Basic Bubble util.Model interface
- layout.Sizeable // Width/height management
- layout.Focusable // Focus state management
- GetToolCall() message.ToolCall // Access to tool call data
- GetToolResult() message.ToolResult // Access to tool result data
- SetToolResult(message.ToolResult) // Update tool result
- SetToolCall(message.ToolCall) // Update tool call
- SetCancelled() // Mark as cancelled
- ParentMessageID() string // Get parent message ID
- Spinning() bool // Animation state for pending tools
- GetNestedToolCalls() []ToolCallCmp // Get nested tool calls
- SetNestedToolCalls([]ToolCallCmp) // Set nested tool calls
- SetIsNested(bool) // Set whether this tool call is nested
- ID() string
- SetPermissionRequested() // Mark permission request
- SetPermissionGranted() // Mark permission granted
- }
- // toolCallCmp implements the ToolCallCmp interface for displaying tool calls.
- // It handles rendering of tool execution states including pending, completed, and error states.
- type toolCallCmp struct {
- width int // Component width for text wrapping
- focused bool // Focus state for border styling
- isNested bool // Whether this tool call is nested within another
- // Tool call data and state
- parentMessageID string // ID of the message that initiated this tool call
- call message.ToolCall // The tool call being executed
- result message.ToolResult // The result of the tool execution
- cancelled bool // Whether the tool call was cancelled
- permissionRequested bool
- permissionGranted bool
- // Animation state for pending tool calls
- spinning bool // Whether to show loading animation
- anim util.Model // Animation component for pending states
- nestedToolCalls []ToolCallCmp // Nested tool calls for hierarchical display
- }
- // ToolCallOption provides functional options for configuring tool call components
- type ToolCallOption func(*toolCallCmp)
- // WithToolCallCancelled marks the tool call as cancelled
- func WithToolCallCancelled() ToolCallOption {
- return func(m *toolCallCmp) {
- m.cancelled = true
- }
- }
- // WithToolCallResult sets the initial tool result
- func WithToolCallResult(result message.ToolResult) ToolCallOption {
- return func(m *toolCallCmp) {
- m.result = result
- }
- }
- func WithToolCallNested(isNested bool) ToolCallOption {
- return func(m *toolCallCmp) {
- m.isNested = isNested
- }
- }
- func WithToolCallNestedCalls(calls []ToolCallCmp) ToolCallOption {
- return func(m *toolCallCmp) {
- m.nestedToolCalls = calls
- }
- }
- func WithToolPermissionRequested() ToolCallOption {
- return func(m *toolCallCmp) {
- m.permissionRequested = true
- }
- }
- func WithToolPermissionGranted() ToolCallOption {
- return func(m *toolCallCmp) {
- m.permissionGranted = true
- }
- }
- // NewToolCallCmp creates a new tool call component with the given parent message ID,
- // tool call, and optional configuration
- func NewToolCallCmp(parentMessageID string, tc message.ToolCall, permissions permission.Service, opts ...ToolCallOption) ToolCallCmp {
- m := &toolCallCmp{
- call: tc,
- parentMessageID: parentMessageID,
- }
- for _, opt := range opts {
- opt(m)
- }
- t := styles.CurrentTheme()
- m.anim = anim.New(anim.Settings{
- Size: 15,
- Label: "Working",
- GradColorA: t.Primary,
- GradColorB: t.Secondary,
- LabelColor: t.FgBase,
- CycleColors: true,
- })
- if m.isNested {
- m.anim = anim.New(anim.Settings{
- Size: 10,
- GradColorA: t.Primary,
- GradColorB: t.Secondary,
- CycleColors: true,
- })
- }
- return m
- }
- // Init initializes the tool call component and starts animations if needed.
- // Returns a command to start the animation for pending tool calls.
- func (m *toolCallCmp) Init() tea.Cmd {
- m.spinning = m.shouldSpin()
- return m.anim.Init()
- }
- // Update handles incoming messages and updates the component state.
- // Manages animation updates for pending tool calls.
- func (m *toolCallCmp) Update(msg tea.Msg) (util.Model, tea.Cmd) {
- switch msg := msg.(type) {
- case anim.StepMsg:
- var cmds []tea.Cmd
- for i, nested := range m.nestedToolCalls {
- if nested.Spinning() {
- u, cmd := nested.Update(msg)
- m.nestedToolCalls[i] = u.(ToolCallCmp)
- cmds = append(cmds, cmd)
- }
- }
- if m.spinning {
- u, cmd := m.anim.Update(msg)
- m.anim = u
- cmds = append(cmds, cmd)
- }
- return m, tea.Batch(cmds...)
- case tea.KeyPressMsg:
- if key.Matches(msg, CopyKey) {
- return m, m.copyTool()
- }
- }
- return m, nil
- }
- // View renders the tool call component based on its current state.
- // Shows either a pending animation or the tool-specific rendered result.
- func (m *toolCallCmp) View() string {
- box := m.style()
- if !m.call.Finished && !m.cancelled {
- return box.Render(m.renderPending())
- }
- r := registry.lookup(m.call.Name)
- if m.isNested {
- return box.Render(r.Render(m))
- }
- return box.Render(r.Render(m))
- }
- // State management methods
- // SetCancelled marks the tool call as cancelled
- func (m *toolCallCmp) SetCancelled() {
- m.cancelled = true
- }
- func (m *toolCallCmp) copyTool() tea.Cmd {
- content := m.formatToolForCopy()
- return tea.Sequence(
- tea.SetClipboard(content),
- func() tea.Msg {
- _ = clipboard.WriteAll(content)
- return nil
- },
- util.ReportInfo("Tool content copied to clipboard"),
- )
- }
- func (m *toolCallCmp) formatToolForCopy() string {
- var parts []string
- toolName := prettifyToolName(m.call.Name)
- parts = append(parts, fmt.Sprintf("## %s Tool Call", toolName))
- if m.call.Input != "" {
- params := m.formatParametersForCopy()
- if params != "" {
- parts = append(parts, "### Parameters:")
- parts = append(parts, params)
- }
- }
- if m.result.ToolCallID != "" {
- if m.result.IsError {
- parts = append(parts, "### Error:")
- parts = append(parts, m.result.Content)
- } else {
- parts = append(parts, "### Result:")
- content := m.formatResultForCopy()
- if content != "" {
- parts = append(parts, content)
- }
- }
- } else if m.cancelled {
- parts = append(parts, "### Status:")
- parts = append(parts, "Cancelled")
- } else {
- parts = append(parts, "### Status:")
- parts = append(parts, "Pending...")
- }
- return strings.Join(parts, "\n\n")
- }
- func (m *toolCallCmp) formatParametersForCopy() string {
- switch m.call.Name {
- case tools.BashToolName:
- var params tools.BashParams
- if json.Unmarshal([]byte(m.call.Input), ¶ms) == nil {
- cmd := strings.ReplaceAll(params.Command, "\n", " ")
- cmd = strings.ReplaceAll(cmd, "\t", " ")
- return fmt.Sprintf("**Command:** %s", cmd)
- }
- case tools.ViewToolName:
- var params tools.ViewParams
- if json.Unmarshal([]byte(m.call.Input), ¶ms) == nil {
- var parts []string
- parts = append(parts, fmt.Sprintf("**File:** %s", fsext.PrettyPath(params.FilePath)))
- if params.Limit > 0 {
- parts = append(parts, fmt.Sprintf("**Limit:** %d", params.Limit))
- }
- if params.Offset > 0 {
- parts = append(parts, fmt.Sprintf("**Offset:** %d", params.Offset))
- }
- return strings.Join(parts, "\n")
- }
- case tools.EditToolName:
- var params tools.EditParams
- if json.Unmarshal([]byte(m.call.Input), ¶ms) == nil {
- return fmt.Sprintf("**File:** %s", fsext.PrettyPath(params.FilePath))
- }
- case tools.MultiEditToolName:
- var params tools.MultiEditParams
- if json.Unmarshal([]byte(m.call.Input), ¶ms) == nil {
- var parts []string
- parts = append(parts, fmt.Sprintf("**File:** %s", fsext.PrettyPath(params.FilePath)))
- parts = append(parts, fmt.Sprintf("**Edits:** %d", len(params.Edits)))
- return strings.Join(parts, "\n")
- }
- case tools.WriteToolName:
- var params tools.WriteParams
- if json.Unmarshal([]byte(m.call.Input), ¶ms) == nil {
- return fmt.Sprintf("**File:** %s", fsext.PrettyPath(params.FilePath))
- }
- case tools.FetchToolName:
- var params tools.FetchParams
- if json.Unmarshal([]byte(m.call.Input), ¶ms) == nil {
- var parts []string
- parts = append(parts, fmt.Sprintf("**URL:** %s", params.URL))
- if params.Format != "" {
- parts = append(parts, fmt.Sprintf("**Format:** %s", params.Format))
- }
- if params.Timeout > 0 {
- parts = append(parts, fmt.Sprintf("**Timeout:** %ds", params.Timeout))
- }
- return strings.Join(parts, "\n")
- }
- case tools.AgenticFetchToolName:
- var params tools.AgenticFetchParams
- if json.Unmarshal([]byte(m.call.Input), ¶ms) == nil {
- var parts []string
- parts = append(parts, fmt.Sprintf("**URL:** %s", params.URL))
- if params.Prompt != "" {
- parts = append(parts, fmt.Sprintf("**Prompt:** %s", params.Prompt))
- }
- return strings.Join(parts, "\n")
- }
- case tools.WebFetchToolName:
- var params tools.WebFetchParams
- if json.Unmarshal([]byte(m.call.Input), ¶ms) == nil {
- return fmt.Sprintf("**URL:** %s", params.URL)
- }
- case tools.GrepToolName:
- var params tools.GrepParams
- if json.Unmarshal([]byte(m.call.Input), ¶ms) == nil {
- var parts []string
- parts = append(parts, fmt.Sprintf("**Pattern:** %s", params.Pattern))
- if params.Path != "" {
- parts = append(parts, fmt.Sprintf("**Path:** %s", params.Path))
- }
- if params.Include != "" {
- parts = append(parts, fmt.Sprintf("**Include:** %s", params.Include))
- }
- if params.LiteralText {
- parts = append(parts, "**Literal:** true")
- }
- return strings.Join(parts, "\n")
- }
- case tools.GlobToolName:
- var params tools.GlobParams
- if json.Unmarshal([]byte(m.call.Input), ¶ms) == nil {
- var parts []string
- parts = append(parts, fmt.Sprintf("**Pattern:** %s", params.Pattern))
- if params.Path != "" {
- parts = append(parts, fmt.Sprintf("**Path:** %s", params.Path))
- }
- return strings.Join(parts, "\n")
- }
- case tools.LSToolName:
- var params tools.LSParams
- if json.Unmarshal([]byte(m.call.Input), ¶ms) == nil {
- path := params.Path
- if path == "" {
- path = "."
- }
- return fmt.Sprintf("**Path:** %s", fsext.PrettyPath(path))
- }
- case tools.DownloadToolName:
- var params tools.DownloadParams
- if json.Unmarshal([]byte(m.call.Input), ¶ms) == nil {
- var parts []string
- parts = append(parts, fmt.Sprintf("**URL:** %s", params.URL))
- parts = append(parts, fmt.Sprintf("**File Path:** %s", fsext.PrettyPath(params.FilePath)))
- if params.Timeout > 0 {
- parts = append(parts, fmt.Sprintf("**Timeout:** %s", (time.Duration(params.Timeout)*time.Second).String()))
- }
- return strings.Join(parts, "\n")
- }
- case tools.SourcegraphToolName:
- var params tools.SourcegraphParams
- if json.Unmarshal([]byte(m.call.Input), ¶ms) == nil {
- var parts []string
- parts = append(parts, fmt.Sprintf("**Query:** %s", params.Query))
- if params.Count > 0 {
- parts = append(parts, fmt.Sprintf("**Count:** %d", params.Count))
- }
- if params.ContextWindow > 0 {
- parts = append(parts, fmt.Sprintf("**Context:** %d", params.ContextWindow))
- }
- return strings.Join(parts, "\n")
- }
- case tools.DiagnosticsToolName:
- return "**Project:** diagnostics"
- case agent.AgentToolName:
- var params agent.AgentParams
- if json.Unmarshal([]byte(m.call.Input), ¶ms) == nil {
- return fmt.Sprintf("**Task:**\n%s", params.Prompt)
- }
- }
- var params map[string]any
- if json.Unmarshal([]byte(m.call.Input), ¶ms) == nil {
- var parts []string
- for key, value := range params {
- displayKey := strings.ReplaceAll(key, "_", " ")
- if len(displayKey) > 0 {
- displayKey = strings.ToUpper(displayKey[:1]) + displayKey[1:]
- }
- parts = append(parts, fmt.Sprintf("**%s:** %v", displayKey, value))
- }
- return strings.Join(parts, "\n")
- }
- return ""
- }
- func (m *toolCallCmp) formatResultForCopy() string {
- switch m.call.Name {
- case tools.BashToolName:
- return m.formatBashResultForCopy()
- case tools.ViewToolName:
- return m.formatViewResultForCopy()
- case tools.EditToolName:
- return m.formatEditResultForCopy()
- case tools.MultiEditToolName:
- return m.formatMultiEditResultForCopy()
- case tools.WriteToolName:
- return m.formatWriteResultForCopy()
- case tools.FetchToolName:
- return m.formatFetchResultForCopy()
- case tools.AgenticFetchToolName:
- return m.formatAgenticFetchResultForCopy()
- case tools.WebFetchToolName:
- return m.formatWebFetchResultForCopy()
- case agent.AgentToolName:
- return m.formatAgentResultForCopy()
- case tools.DownloadToolName, tools.GrepToolName, tools.GlobToolName, tools.LSToolName, tools.SourcegraphToolName, tools.DiagnosticsToolName:
- return fmt.Sprintf("```\n%s\n```", m.result.Content)
- default:
- return m.result.Content
- }
- }
- func (m *toolCallCmp) formatBashResultForCopy() string {
- var meta tools.BashResponseMetadata
- if m.result.Metadata != "" {
- json.Unmarshal([]byte(m.result.Metadata), &meta)
- }
- output := meta.Output
- if output == "" && m.result.Content != tools.BashNoOutput {
- output = m.result.Content
- }
- if output == "" {
- return ""
- }
- return fmt.Sprintf("```bash\n%s\n```", output)
- }
- func (m *toolCallCmp) formatViewResultForCopy() string {
- var meta tools.ViewResponseMetadata
- if m.result.Metadata != "" {
- json.Unmarshal([]byte(m.result.Metadata), &meta)
- }
- if meta.Content == "" {
- return m.result.Content
- }
- lang := ""
- if meta.FilePath != "" {
- ext := strings.ToLower(filepath.Ext(meta.FilePath))
- switch ext {
- case ".go":
- lang = "go"
- case ".js", ".mjs":
- lang = "javascript"
- case ".ts":
- lang = "typescript"
- case ".py":
- lang = "python"
- case ".rs":
- lang = "rust"
- case ".java":
- lang = "java"
- case ".c":
- lang = "c"
- case ".cpp", ".cc", ".cxx":
- lang = "cpp"
- case ".sh", ".bash":
- lang = "bash"
- case ".json":
- lang = "json"
- case ".yaml", ".yml":
- lang = "yaml"
- case ".xml":
- lang = "xml"
- case ".html":
- lang = "html"
- case ".css":
- lang = "css"
- case ".md":
- lang = "markdown"
- }
- }
- var result strings.Builder
- if lang != "" {
- result.WriteString(fmt.Sprintf("```%s\n", lang))
- } else {
- result.WriteString("```\n")
- }
- result.WriteString(meta.Content)
- result.WriteString("\n```")
- return result.String()
- }
- func (m *toolCallCmp) formatEditResultForCopy() string {
- var meta tools.EditResponseMetadata
- if m.result.Metadata == "" {
- return m.result.Content
- }
- if json.Unmarshal([]byte(m.result.Metadata), &meta) != nil {
- return m.result.Content
- }
- var params tools.EditParams
- json.Unmarshal([]byte(m.call.Input), ¶ms)
- var result strings.Builder
- if meta.OldContent != "" || meta.NewContent != "" {
- fileName := params.FilePath
- if fileName != "" {
- fileName = fsext.PrettyPath(fileName)
- }
- diffContent, additions, removals := diff.GenerateDiff(meta.OldContent, meta.NewContent, fileName)
- result.WriteString(fmt.Sprintf("Changes: +%d -%d\n", additions, removals))
- result.WriteString("```diff\n")
- result.WriteString(diffContent)
- result.WriteString("\n```")
- }
- return result.String()
- }
- func (m *toolCallCmp) formatMultiEditResultForCopy() string {
- var meta tools.MultiEditResponseMetadata
- if m.result.Metadata == "" {
- return m.result.Content
- }
- if json.Unmarshal([]byte(m.result.Metadata), &meta) != nil {
- return m.result.Content
- }
- var params tools.MultiEditParams
- json.Unmarshal([]byte(m.call.Input), ¶ms)
- var result strings.Builder
- if meta.OldContent != "" || meta.NewContent != "" {
- fileName := params.FilePath
- if fileName != "" {
- fileName = fsext.PrettyPath(fileName)
- }
- diffContent, additions, removals := diff.GenerateDiff(meta.OldContent, meta.NewContent, fileName)
- result.WriteString(fmt.Sprintf("Changes: +%d -%d\n", additions, removals))
- result.WriteString("```diff\n")
- result.WriteString(diffContent)
- result.WriteString("\n```")
- }
- return result.String()
- }
- func (m *toolCallCmp) formatWriteResultForCopy() string {
- var params tools.WriteParams
- if json.Unmarshal([]byte(m.call.Input), ¶ms) != nil {
- return m.result.Content
- }
- lang := ""
- if params.FilePath != "" {
- ext := strings.ToLower(filepath.Ext(params.FilePath))
- switch ext {
- case ".go":
- lang = "go"
- case ".js", ".mjs":
- lang = "javascript"
- case ".ts":
- lang = "typescript"
- case ".py":
- lang = "python"
- case ".rs":
- lang = "rust"
- case ".java":
- lang = "java"
- case ".c":
- lang = "c"
- case ".cpp", ".cc", ".cxx":
- lang = "cpp"
- case ".sh", ".bash":
- lang = "bash"
- case ".json":
- lang = "json"
- case ".yaml", ".yml":
- lang = "yaml"
- case ".xml":
- lang = "xml"
- case ".html":
- lang = "html"
- case ".css":
- lang = "css"
- case ".md":
- lang = "markdown"
- }
- }
- var result strings.Builder
- result.WriteString(fmt.Sprintf("File: %s\n", fsext.PrettyPath(params.FilePath)))
- if lang != "" {
- result.WriteString(fmt.Sprintf("```%s\n", lang))
- } else {
- result.WriteString("```\n")
- }
- result.WriteString(params.Content)
- result.WriteString("\n```")
- return result.String()
- }
- func (m *toolCallCmp) formatFetchResultForCopy() string {
- var params tools.FetchParams
- if json.Unmarshal([]byte(m.call.Input), ¶ms) != nil {
- return m.result.Content
- }
- var result strings.Builder
- if params.URL != "" {
- result.WriteString(fmt.Sprintf("URL: %s\n", params.URL))
- }
- if params.Format != "" {
- result.WriteString(fmt.Sprintf("Format: %s\n", params.Format))
- }
- if params.Timeout > 0 {
- result.WriteString(fmt.Sprintf("Timeout: %ds\n", params.Timeout))
- }
- result.WriteString("\n")
- result.WriteString(m.result.Content)
- return result.String()
- }
- func (m *toolCallCmp) formatAgenticFetchResultForCopy() string {
- var params tools.AgenticFetchParams
- if json.Unmarshal([]byte(m.call.Input), ¶ms) != nil {
- return m.result.Content
- }
- var result strings.Builder
- if params.URL != "" {
- result.WriteString(fmt.Sprintf("URL: %s\n", params.URL))
- }
- if params.Prompt != "" {
- result.WriteString(fmt.Sprintf("Prompt: %s\n\n", params.Prompt))
- }
- result.WriteString("```markdown\n")
- result.WriteString(m.result.Content)
- result.WriteString("\n```")
- return result.String()
- }
- func (m *toolCallCmp) formatWebFetchResultForCopy() string {
- var params tools.WebFetchParams
- if json.Unmarshal([]byte(m.call.Input), ¶ms) != nil {
- return m.result.Content
- }
- var result strings.Builder
- result.WriteString(fmt.Sprintf("URL: %s\n\n", params.URL))
- result.WriteString("```markdown\n")
- result.WriteString(m.result.Content)
- result.WriteString("\n```")
- return result.String()
- }
- func (m *toolCallCmp) formatAgentResultForCopy() string {
- var result strings.Builder
- if len(m.nestedToolCalls) > 0 {
- result.WriteString("### Nested Tool Calls:\n")
- for i, nestedCall := range m.nestedToolCalls {
- nestedContent := nestedCall.(*toolCallCmp).formatToolForCopy()
- indentedContent := strings.ReplaceAll(nestedContent, "\n", "\n ")
- result.WriteString(fmt.Sprintf("%d. %s\n", i+1, indentedContent))
- if i < len(m.nestedToolCalls)-1 {
- result.WriteString("\n")
- }
- }
- if m.result.Content != "" {
- result.WriteString("\n### Final Result:\n")
- }
- }
- if m.result.Content != "" {
- result.WriteString(fmt.Sprintf("```markdown\n%s\n```", m.result.Content))
- }
- return result.String()
- }
- // SetToolCall updates the tool call data and stops spinning if finished
- func (m *toolCallCmp) SetToolCall(call message.ToolCall) {
- m.call = call
- if m.call.Finished {
- m.spinning = false
- }
- }
- // ParentMessageID returns the ID of the message that initiated this tool call
- func (m *toolCallCmp) ParentMessageID() string {
- return m.parentMessageID
- }
- // SetToolResult updates the tool result and stops the spinning animation
- func (m *toolCallCmp) SetToolResult(result message.ToolResult) {
- m.result = result
- m.spinning = false
- }
- // GetToolCall returns the current tool call data
- func (m *toolCallCmp) GetToolCall() message.ToolCall {
- return m.call
- }
- // GetToolResult returns the current tool result data
- func (m *toolCallCmp) GetToolResult() message.ToolResult {
- return m.result
- }
- // GetNestedToolCalls returns the nested tool calls
- func (m *toolCallCmp) GetNestedToolCalls() []ToolCallCmp {
- return m.nestedToolCalls
- }
- // SetNestedToolCalls sets the nested tool calls
- func (m *toolCallCmp) SetNestedToolCalls(calls []ToolCallCmp) {
- m.nestedToolCalls = calls
- for _, nested := range m.nestedToolCalls {
- nested.SetSize(m.width, 0)
- }
- }
- // SetIsNested sets whether this tool call is nested within another
- func (m *toolCallCmp) SetIsNested(isNested bool) {
- m.isNested = isNested
- }
- // Rendering methods
- // renderPending displays the tool name with a loading animation for pending tool calls
- func (m *toolCallCmp) renderPending() string {
- t := styles.CurrentTheme()
- icon := t.S().Base.Foreground(t.GreenDark).Render(styles.ToolPending)
- if m.isNested {
- tool := t.S().Base.Foreground(t.FgHalfMuted).Render(prettifyToolName(m.call.Name))
- return fmt.Sprintf("%s %s %s", icon, tool, m.anim.View())
- }
- tool := t.S().Base.Foreground(t.Blue).Render(prettifyToolName(m.call.Name))
- return fmt.Sprintf("%s %s %s", icon, tool, m.anim.View())
- }
- // style returns the lipgloss style for the tool call component.
- // Applies muted colors and focus-dependent border styles.
- func (m *toolCallCmp) style() lipgloss.Style {
- t := styles.CurrentTheme()
- if m.isNested {
- return t.S().Muted
- }
- style := t.S().Muted.PaddingLeft(2)
- if m.focused {
- style = style.PaddingLeft(1).BorderStyle(focusedMessageBorder).BorderLeft(true).BorderForeground(t.GreenDark)
- }
- return style
- }
- // textWidth calculates the available width for text content,
- // accounting for borders and padding
- func (m *toolCallCmp) textWidth() int {
- if m.isNested {
- return m.width - 6
- }
- return m.width - 5 // take into account the border and PaddingLeft
- }
- // fit truncates content to fit within the specified width with ellipsis
- func (m *toolCallCmp) fit(content string, width int) string {
- t := styles.CurrentTheme()
- lineStyle := t.S().Muted
- dots := lineStyle.Render("…")
- return ansi.Truncate(content, width, dots)
- }
- // Focus management methods
- // Blur removes focus from the tool call component
- func (m *toolCallCmp) Blur() tea.Cmd {
- m.focused = false
- return nil
- }
- // Focus sets focus on the tool call component
- func (m *toolCallCmp) Focus() tea.Cmd {
- m.focused = true
- return nil
- }
- // IsFocused returns whether the tool call component is currently focused
- func (m *toolCallCmp) IsFocused() bool {
- return m.focused
- }
- // Size management methods
- // GetSize returns the current dimensions of the tool call component
- func (m *toolCallCmp) GetSize() (int, int) {
- return m.width, 0
- }
- // SetSize updates the width of the tool call component for text wrapping
- func (m *toolCallCmp) SetSize(width int, height int) tea.Cmd {
- m.width = width
- for _, nested := range m.nestedToolCalls {
- nested.SetSize(width, height)
- }
- return nil
- }
- // shouldSpin determines whether the tool call should show a loading animation.
- // Returns true if the tool call is not finished or if the result doesn't match the call ID.
- func (m *toolCallCmp) shouldSpin() bool {
- return !m.call.Finished && !m.cancelled
- }
- // Spinning returns whether the tool call is currently showing a loading animation
- func (m *toolCallCmp) Spinning() bool {
- if m.spinning {
- return true
- }
- for _, nested := range m.nestedToolCalls {
- if nested.Spinning() {
- return true
- }
- }
- return m.spinning
- }
- func (m *toolCallCmp) ID() string {
- return m.call.ID
- }
- // SetPermissionRequested marks that a permission request was made for this tool call
- func (m *toolCallCmp) SetPermissionRequested() {
- m.permissionRequested = true
- }
- // SetPermissionGranted marks that permission was granted for this tool call
- func (m *toolCallCmp) SetPermissionGranted() {
- m.permissionGranted = true
- }
|