message.go 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706
  1. package chat
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "path/filepath"
  6. "strings"
  7. "time"
  8. "github.com/charmbracelet/lipgloss"
  9. "github.com/charmbracelet/x/ansi"
  10. "github.com/sst/opencode/internal/config"
  11. "github.com/sst/opencode/internal/diff"
  12. "github.com/sst/opencode/internal/llm/tools"
  13. "github.com/sst/opencode/internal/message"
  14. "github.com/sst/opencode/internal/tui/styles"
  15. "github.com/sst/opencode/internal/tui/theme"
  16. "github.com/sst/opencode/pkg/client"
  17. )
  18. type uiMessageType int
  19. const (
  20. maxResultHeight = 10
  21. )
  22. func toMarkdown(content string, width int) string {
  23. r := styles.GetMarkdownRenderer(width)
  24. rendered, _ := r.Render(content)
  25. return strings.TrimSuffix(rendered, "\n")
  26. }
  27. func renderUserMessage(msg client.MessageInfo, width int) string {
  28. t := theme.CurrentTheme()
  29. style := styles.BaseStyle().
  30. BorderLeft(true).
  31. Foreground(t.TextMuted()).
  32. BorderForeground(t.Secondary()).
  33. BorderStyle(lipgloss.ThickBorder())
  34. baseStyle := styles.BaseStyle()
  35. // var styledAttachments []string
  36. // attachmentStyles := baseStyle.
  37. // MarginLeft(1).
  38. // Background(t.TextMuted()).
  39. // Foreground(t.Text())
  40. // for _, attachment := range msg.BinaryContent() {
  41. // file := filepath.Base(attachment.Path)
  42. // var filename string
  43. // if len(file) > 10 {
  44. // filename = fmt.Sprintf(" %s %s...", styles.DocumentIcon, file[0:7])
  45. // } else {
  46. // filename = fmt.Sprintf(" %s %s", styles.DocumentIcon, file)
  47. // }
  48. // styledAttachments = append(styledAttachments, attachmentStyles.Render(filename))
  49. // }
  50. // Add timestamp info
  51. timestamp := time.UnixMilli(int64(msg.Metadata.Time.Created)).Local().Format("02 Jan 2006 03:04 PM")
  52. username, _ := config.GetUsername()
  53. info := baseStyle.
  54. Foreground(t.TextMuted()).
  55. Render(fmt.Sprintf(" %s (%s)", username, timestamp))
  56. content := ""
  57. // if len(styledAttachments) > 0 {
  58. // attachmentContent := baseStyle.Width(width).Render(lipgloss.JoinHorizontal(lipgloss.Left, styledAttachments...))
  59. // content = renderMessage(msg.Content().String(), true, isFocused, width, append(info, attachmentContent)...)
  60. // } else {
  61. for _, p := range msg.Parts {
  62. part, err := p.ValueByDiscriminator()
  63. if err != nil {
  64. continue //TODO: handle error?
  65. }
  66. switch part.(type) {
  67. case client.MessagePartText:
  68. textPart := part.(client.MessagePartText)
  69. text := toMarkdown(textPart.Text, width)
  70. content = style.Render(lipgloss.JoinVertical(lipgloss.Left, text, info))
  71. }
  72. }
  73. return content
  74. }
  75. func convertToMap(input *any) (map[string]any, bool) {
  76. if input == nil {
  77. return nil, false // Handle nil pointer
  78. }
  79. value := *input // Dereference the pointer to get the interface value
  80. m, ok := value.(map[string]any) // Type assertion
  81. return m, ok
  82. }
  83. func renderAssistantMessage(
  84. msg client.MessageInfo,
  85. width int,
  86. showToolMessages bool,
  87. ) string {
  88. t := theme.CurrentTheme()
  89. style := styles.BaseStyle().
  90. BorderLeft(true).
  91. Foreground(t.TextMuted()).
  92. BorderForeground(t.Primary()).
  93. BorderStyle(lipgloss.ThickBorder())
  94. toolStyle := styles.BaseStyle().
  95. BorderLeft(true).
  96. Foreground(t.TextMuted()).
  97. BorderForeground(t.TextMuted()).
  98. BorderStyle(lipgloss.ThickBorder())
  99. baseStyle := styles.BaseStyle()
  100. messages := []string{}
  101. // content := strings.TrimSpace(msg.Content().String())
  102. // thinking := msg.IsThinking()
  103. // thinkingContent := msg.ReasoningContent().Thinking
  104. // finished := msg.IsFinished()
  105. // finishData := msg.FinishPart()
  106. // Add timestamp info
  107. timestamp := time.UnixMilli(int64(msg.Metadata.Time.Created)).Local().Format("02 Jan 2006 03:04 PM")
  108. modelName := msg.Metadata.Assistant.ModelID
  109. info := baseStyle.
  110. Foreground(t.TextMuted()).
  111. Render(fmt.Sprintf(" %s (%s)", modelName, timestamp))
  112. for _, p := range msg.Parts {
  113. part, err := p.ValueByDiscriminator()
  114. if err != nil {
  115. continue //TODO: handle error?
  116. }
  117. switch part.(type) {
  118. case client.MessagePartText:
  119. textPart := part.(client.MessagePartText)
  120. text := toMarkdown(textPart.Text, width)
  121. content := style.Render(lipgloss.JoinVertical(lipgloss.Left, text, info))
  122. messages = append(messages, content)
  123. case client.MessagePartToolInvocation:
  124. if !showToolMessages {
  125. continue
  126. }
  127. toolInvocationPart := part.(client.MessagePartToolInvocation)
  128. toolInvocation, _ := toolInvocationPart.ToolInvocation.ValueByDiscriminator()
  129. switch toolInvocation.(type) {
  130. case client.MessageToolInvocationToolCall:
  131. toolCall := toolInvocation.(client.MessageToolInvocationToolCall)
  132. toolName := toolName(toolCall.ToolName)
  133. var toolArgs []string
  134. toolMap, _ := convertToMap(toolCall.Args)
  135. for _, arg := range toolMap {
  136. toolArgs = append(toolArgs, fmt.Sprintf("%v", arg))
  137. }
  138. params := renderParams(width-lipgloss.Width(toolName)-1, toolArgs...)
  139. title := styles.Padded().Render(fmt.Sprintf("%s: %s", toolName, params))
  140. content := toolStyle.Render(lipgloss.JoinVertical(lipgloss.Left,
  141. title,
  142. " In progress...",
  143. ))
  144. messages = append(messages, content)
  145. case client.MessageToolInvocationToolResult:
  146. toolInvocationResult := toolInvocation.(client.MessageToolInvocationToolResult)
  147. toolName := toolName(toolInvocationResult.ToolName)
  148. var toolArgs []string
  149. toolMap, _ := convertToMap(toolInvocationResult.Args)
  150. for _, arg := range toolMap {
  151. toolArgs = append(toolArgs, fmt.Sprintf("%v", arg))
  152. }
  153. result := truncateHeight(strings.TrimSpace(toolInvocationResult.Result), 10)
  154. params := renderParams(width-lipgloss.Width(toolName)-1, toolArgs...)
  155. title := styles.Padded().Render(fmt.Sprintf("%s: %s", toolName, params))
  156. markdown := toMarkdown(result, width)
  157. content := toolStyle.Render(lipgloss.JoinVertical(lipgloss.Left,
  158. title,
  159. markdown,
  160. ))
  161. messages = append(messages, content)
  162. }
  163. }
  164. }
  165. // if finished {
  166. // // Add finish info if available
  167. // switch finishData.Reason {
  168. // case message.FinishReasonCanceled:
  169. // info = append(info, baseStyle.
  170. // Width(width-1).
  171. // Foreground(t.Warning()).
  172. // Render("(canceled)"),
  173. // )
  174. // case message.FinishReasonError:
  175. // info = append(info, baseStyle.
  176. // Width(width-1).
  177. // Foreground(t.Error()).
  178. // Render("(error)"),
  179. // )
  180. // case message.FinishReasonPermissionDenied:
  181. // info = append(info, baseStyle.
  182. // Width(width-1).
  183. // Foreground(t.Info()).
  184. // Render("(permission denied)"),
  185. // )
  186. // }
  187. // }
  188. // if content != "" || (finished && finishData.Reason == message.FinishReasonEndTurn) {
  189. // if content == "" {
  190. // content = "*Finished without output*"
  191. // }
  192. //
  193. // content = renderMessage(content, false, width, info...)
  194. // messages = append(messages, content)
  195. // // position += messages[0].height
  196. // position++ // for the space
  197. // } else if thinking && thinkingContent != "" {
  198. // // Render the thinking content with timestamp
  199. // content = renderMessage(thinkingContent, false, width, info...)
  200. // messages = append(messages, content)
  201. // position += lipgloss.Height(content)
  202. // position++ // for the space
  203. // }
  204. // Only render tool messages if they should be shown
  205. if showToolMessages {
  206. // for i, toolCall := range msg.ToolCalls() {
  207. // toolCallContent := renderToolMessage(
  208. // toolCall,
  209. // allMessages,
  210. // messagesService,
  211. // focusedUIMessageId,
  212. // false,
  213. // width,
  214. // i+1,
  215. // )
  216. // messages = append(messages, toolCallContent)
  217. // }
  218. }
  219. return strings.Join(messages, "\n\n")
  220. }
  221. func findToolResponse(toolCallID string, futureMessages []message.Message) *message.ToolResult {
  222. for _, msg := range futureMessages {
  223. for _, result := range msg.ToolResults() {
  224. if result.ToolCallID == toolCallID {
  225. return &result
  226. }
  227. }
  228. }
  229. return nil
  230. }
  231. func toolName(name string) string {
  232. switch name {
  233. // case agent.AgentToolName:
  234. // return "Task"
  235. case tools.BashToolName:
  236. return "Bash"
  237. case tools.EditToolName:
  238. return "Edit"
  239. case tools.FetchToolName:
  240. return "Fetch"
  241. case tools.GlobToolName:
  242. return "Glob"
  243. case tools.GrepToolName:
  244. return "Grep"
  245. case tools.LSToolName:
  246. return "List"
  247. case tools.ViewToolName:
  248. return "View"
  249. case tools.WriteToolName:
  250. return "Write"
  251. case tools.PatchToolName:
  252. return "Patch"
  253. case tools.BatchToolName:
  254. return "Batch"
  255. }
  256. return name
  257. }
  258. func getToolAction(name string) string {
  259. switch name {
  260. // case agent.AgentToolName:
  261. // return "Preparing prompt..."
  262. case tools.BashToolName:
  263. return "Building command..."
  264. case tools.EditToolName:
  265. return "Preparing edit..."
  266. case tools.FetchToolName:
  267. return "Writing fetch..."
  268. case tools.GlobToolName:
  269. return "Finding files..."
  270. case tools.GrepToolName:
  271. return "Searching content..."
  272. case tools.LSToolName:
  273. return "Listing directory..."
  274. case tools.ViewToolName:
  275. return "Reading file..."
  276. case tools.WriteToolName:
  277. return "Preparing write..."
  278. case tools.PatchToolName:
  279. return "Preparing patch..."
  280. case tools.BatchToolName:
  281. return "Running batch operations..."
  282. }
  283. return "Working..."
  284. }
  285. // renders params, params[0] (params[1]=params[2] ....)
  286. func renderParams(paramsWidth int, params ...string) string {
  287. if len(params) == 0 {
  288. return ""
  289. }
  290. mainParam := params[0]
  291. if len(mainParam) > paramsWidth {
  292. mainParam = mainParam[:paramsWidth-3] + "..."
  293. }
  294. if len(params) == 1 {
  295. return mainParam
  296. }
  297. otherParams := params[1:]
  298. // create pairs of key/value
  299. // if odd number of params, the last one is a key without value
  300. if len(otherParams)%2 != 0 {
  301. otherParams = append(otherParams, "")
  302. }
  303. parts := make([]string, 0, len(otherParams)/2)
  304. for i := 0; i < len(otherParams); i += 2 {
  305. key := otherParams[i]
  306. value := otherParams[i+1]
  307. if value == "" {
  308. continue
  309. }
  310. parts = append(parts, fmt.Sprintf("%s=%s", key, value))
  311. }
  312. partsRendered := strings.Join(parts, ", ")
  313. remainingWidth := paramsWidth - lipgloss.Width(partsRendered) - 5 // for the space
  314. if remainingWidth < 30 {
  315. // No space for the params, just show the main
  316. return mainParam
  317. }
  318. if len(parts) > 0 {
  319. mainParam = fmt.Sprintf("%s (%s)", mainParam, strings.Join(parts, ", "))
  320. }
  321. return ansi.Truncate(mainParam, paramsWidth, "...")
  322. }
  323. func removeWorkingDirPrefix(path string) string {
  324. wd := config.WorkingDirectory()
  325. if strings.HasPrefix(path, wd) {
  326. path = strings.TrimPrefix(path, wd)
  327. }
  328. if strings.HasPrefix(path, "/") {
  329. path = strings.TrimPrefix(path, "/")
  330. }
  331. if strings.HasPrefix(path, "./") {
  332. path = strings.TrimPrefix(path, "./")
  333. }
  334. if strings.HasPrefix(path, "../") {
  335. path = strings.TrimPrefix(path, "../")
  336. }
  337. return path
  338. }
  339. func renderToolParams(paramWidth int, toolCall message.ToolCall) string {
  340. params := ""
  341. switch toolCall.Name {
  342. // case agent.AgentToolName:
  343. // var params agent.AgentParams
  344. // json.Unmarshal([]byte(toolCall.Input), &params)
  345. // prompt := strings.ReplaceAll(params.Prompt, "\n", " ")
  346. // return renderParams(paramWidth, prompt)
  347. case tools.BashToolName:
  348. var params tools.BashParams
  349. json.Unmarshal([]byte(toolCall.Input), &params)
  350. command := strings.ReplaceAll(params.Command, "\n", " ")
  351. return renderParams(paramWidth, command)
  352. case tools.EditToolName:
  353. var params tools.EditParams
  354. json.Unmarshal([]byte(toolCall.Input), &params)
  355. filePath := removeWorkingDirPrefix(params.FilePath)
  356. return renderParams(paramWidth, filePath)
  357. case tools.FetchToolName:
  358. var params tools.FetchParams
  359. json.Unmarshal([]byte(toolCall.Input), &params)
  360. url := params.URL
  361. toolParams := []string{
  362. url,
  363. }
  364. if params.Format != "" {
  365. toolParams = append(toolParams, "format", params.Format)
  366. }
  367. if params.Timeout != 0 {
  368. toolParams = append(toolParams, "timeout", (time.Duration(params.Timeout) * time.Second).String())
  369. }
  370. return renderParams(paramWidth, toolParams...)
  371. case tools.GlobToolName:
  372. var params tools.GlobParams
  373. json.Unmarshal([]byte(toolCall.Input), &params)
  374. pattern := params.Pattern
  375. toolParams := []string{
  376. pattern,
  377. }
  378. if params.Path != "" {
  379. toolParams = append(toolParams, "path", params.Path)
  380. }
  381. return renderParams(paramWidth, toolParams...)
  382. case tools.GrepToolName:
  383. var params tools.GrepParams
  384. json.Unmarshal([]byte(toolCall.Input), &params)
  385. pattern := params.Pattern
  386. toolParams := []string{
  387. pattern,
  388. }
  389. if params.Path != "" {
  390. toolParams = append(toolParams, "path", params.Path)
  391. }
  392. if params.Include != "" {
  393. toolParams = append(toolParams, "include", params.Include)
  394. }
  395. if params.LiteralText {
  396. toolParams = append(toolParams, "literal", "true")
  397. }
  398. return renderParams(paramWidth, toolParams...)
  399. case tools.LSToolName:
  400. var params tools.LSParams
  401. json.Unmarshal([]byte(toolCall.Input), &params)
  402. path := params.Path
  403. if path == "" {
  404. path = "."
  405. }
  406. return renderParams(paramWidth, path)
  407. case tools.ViewToolName:
  408. var params tools.ViewParams
  409. json.Unmarshal([]byte(toolCall.Input), &params)
  410. filePath := removeWorkingDirPrefix(params.FilePath)
  411. toolParams := []string{
  412. filePath,
  413. }
  414. if params.Limit != 0 {
  415. toolParams = append(toolParams, "limit", fmt.Sprintf("%d", params.Limit))
  416. }
  417. if params.Offset != 0 {
  418. toolParams = append(toolParams, "offset", fmt.Sprintf("%d", params.Offset))
  419. }
  420. return renderParams(paramWidth, toolParams...)
  421. case tools.WriteToolName:
  422. var params tools.WriteParams
  423. json.Unmarshal([]byte(toolCall.Input), &params)
  424. filePath := removeWorkingDirPrefix(params.FilePath)
  425. return renderParams(paramWidth, filePath)
  426. case tools.BatchToolName:
  427. var params tools.BatchParams
  428. json.Unmarshal([]byte(toolCall.Input), &params)
  429. return renderParams(paramWidth, fmt.Sprintf("%d parallel calls", len(params.Calls)))
  430. default:
  431. input := strings.ReplaceAll(toolCall.Input, "\n", " ")
  432. params = renderParams(paramWidth, input)
  433. }
  434. return params
  435. }
  436. func truncateHeight(content string, height int) string {
  437. lines := strings.Split(content, "\n")
  438. if len(lines) > height {
  439. return strings.Join(lines[:height], "\n")
  440. }
  441. return content
  442. }
  443. func renderToolResponse(toolCall message.ToolCall, response message.ToolResult, width int) string {
  444. t := theme.CurrentTheme()
  445. baseStyle := styles.BaseStyle()
  446. if response.IsError {
  447. errContent := fmt.Sprintf("Error: %s", strings.ReplaceAll(response.Content, "\n", " "))
  448. errContent = ansi.Truncate(errContent, width-1, "...")
  449. return baseStyle.
  450. Width(width).
  451. Foreground(t.Error()).
  452. Render(errContent)
  453. }
  454. resultContent := truncateHeight(response.Content, maxResultHeight)
  455. switch toolCall.Name {
  456. // case agent.AgentToolName:
  457. // return styles.ForceReplaceBackgroundWithLipgloss(
  458. // toMarkdown(resultContent, false, width),
  459. // t.Background(),
  460. // )
  461. case tools.BashToolName:
  462. resultContent = fmt.Sprintf("```bash\n%s\n```", resultContent)
  463. return styles.ForceReplaceBackgroundWithLipgloss(
  464. toMarkdown(resultContent, width),
  465. t.Background(),
  466. )
  467. case tools.EditToolName:
  468. metadata := tools.EditResponseMetadata{}
  469. json.Unmarshal([]byte(response.Metadata), &metadata)
  470. formattedDiff, _ := diff.FormatDiff(metadata.Diff, diff.WithTotalWidth(width))
  471. return formattedDiff
  472. case tools.FetchToolName:
  473. var params tools.FetchParams
  474. json.Unmarshal([]byte(toolCall.Input), &params)
  475. mdFormat := "markdown"
  476. switch params.Format {
  477. case "text":
  478. mdFormat = "text"
  479. case "html":
  480. mdFormat = "html"
  481. }
  482. resultContent = fmt.Sprintf("```%s\n%s\n```", mdFormat, resultContent)
  483. return styles.ForceReplaceBackgroundWithLipgloss(
  484. toMarkdown(resultContent, width),
  485. t.Background(),
  486. )
  487. case tools.GlobToolName:
  488. return baseStyle.Width(width).Foreground(t.TextMuted()).Render(resultContent)
  489. case tools.GrepToolName:
  490. return baseStyle.Width(width).Foreground(t.TextMuted()).Render(resultContent)
  491. case tools.LSToolName:
  492. return baseStyle.Width(width).Foreground(t.TextMuted()).Render(resultContent)
  493. case tools.ViewToolName:
  494. metadata := tools.ViewResponseMetadata{}
  495. json.Unmarshal([]byte(response.Metadata), &metadata)
  496. ext := filepath.Ext(metadata.FilePath)
  497. if ext == "" {
  498. ext = ""
  499. } else {
  500. ext = strings.ToLower(ext[1:])
  501. }
  502. resultContent = fmt.Sprintf("```%s\n%s\n```", ext, truncateHeight(metadata.Content, maxResultHeight))
  503. return styles.ForceReplaceBackgroundWithLipgloss(
  504. toMarkdown(resultContent, width),
  505. t.Background(),
  506. )
  507. case tools.WriteToolName:
  508. params := tools.WriteParams{}
  509. json.Unmarshal([]byte(toolCall.Input), &params)
  510. metadata := tools.WriteResponseMetadata{}
  511. json.Unmarshal([]byte(response.Metadata), &metadata)
  512. ext := filepath.Ext(params.FilePath)
  513. if ext == "" {
  514. ext = ""
  515. } else {
  516. ext = strings.ToLower(ext[1:])
  517. }
  518. resultContent = fmt.Sprintf("```%s\n%s\n```", ext, truncateHeight(params.Content, maxResultHeight))
  519. return styles.ForceReplaceBackgroundWithLipgloss(
  520. toMarkdown(resultContent, width),
  521. t.Background(),
  522. )
  523. case tools.BatchToolName:
  524. var batchResult tools.BatchResult
  525. if err := json.Unmarshal([]byte(resultContent), &batchResult); err != nil {
  526. return baseStyle.Width(width).Foreground(t.Error()).Render(fmt.Sprintf("Error parsing batch result: %s", err))
  527. }
  528. var toolCalls []string
  529. for i, result := range batchResult.Results {
  530. toolName := toolName(result.ToolName)
  531. // Format the tool input as a string
  532. inputStr := string(result.ToolInput)
  533. // Format the result
  534. var resultStr string
  535. if result.Error != "" {
  536. resultStr = fmt.Sprintf("Error: %s", result.Error)
  537. } else {
  538. var toolResponse tools.ToolResponse
  539. if err := json.Unmarshal(result.Result, &toolResponse); err != nil {
  540. resultStr = "Error parsing tool response"
  541. } else {
  542. resultStr = truncateHeight(toolResponse.Content, 3)
  543. }
  544. }
  545. // Format the tool call
  546. toolCall := fmt.Sprintf("%d. %s: %s\n %s", i+1, toolName, inputStr, resultStr)
  547. toolCalls = append(toolCalls, toolCall)
  548. }
  549. return baseStyle.Width(width).Foreground(t.TextMuted()).Render(strings.Join(toolCalls, "\n\n"))
  550. default:
  551. resultContent = fmt.Sprintf("```text\n%s\n```", resultContent)
  552. return styles.ForceReplaceBackgroundWithLipgloss(
  553. toMarkdown(resultContent, width),
  554. t.Background(),
  555. )
  556. }
  557. }
  558. func renderToolMessage(
  559. toolCall message.ToolCall,
  560. allMessages []message.Message,
  561. messagesService message.Service,
  562. focusedUIMessageId string,
  563. nested bool,
  564. width int,
  565. position int,
  566. ) string {
  567. if nested {
  568. width = width - 3
  569. }
  570. t := theme.CurrentTheme()
  571. baseStyle := styles.BaseStyle()
  572. style := baseStyle.
  573. Width(width - 1).
  574. BorderLeft(true).
  575. BorderStyle(lipgloss.ThickBorder()).
  576. PaddingLeft(1).
  577. BorderForeground(t.TextMuted())
  578. response := findToolResponse(toolCall.ID, allMessages)
  579. toolNameText := baseStyle.Foreground(t.TextMuted()).
  580. Render(fmt.Sprintf("%s: ", toolName(toolCall.Name)))
  581. if !toolCall.Finished {
  582. // Get a brief description of what the tool is doing
  583. toolAction := getToolAction(toolCall.Name)
  584. progressText := baseStyle.
  585. Width(width - 2 - lipgloss.Width(toolNameText)).
  586. Foreground(t.TextMuted()).
  587. Render(fmt.Sprintf("%s", toolAction))
  588. content := style.Render(lipgloss.JoinHorizontal(lipgloss.Left, toolNameText, progressText))
  589. return content
  590. }
  591. params := renderToolParams(width-1-lipgloss.Width(toolNameText), toolCall)
  592. responseContent := ""
  593. if response != nil {
  594. responseContent = renderToolResponse(toolCall, *response, width-2)
  595. responseContent = strings.TrimSuffix(responseContent, "\n")
  596. } else {
  597. responseContent = baseStyle.
  598. Italic(true).
  599. Width(width - 2).
  600. Foreground(t.TextMuted()).
  601. Render("Waiting for response...")
  602. }
  603. parts := []string{}
  604. if !nested {
  605. formattedParams := baseStyle.
  606. Width(width - 2 - lipgloss.Width(toolNameText)).
  607. Foreground(t.TextMuted()).
  608. Render(params)
  609. parts = append(parts, lipgloss.JoinHorizontal(lipgloss.Left, toolNameText, formattedParams))
  610. } else {
  611. prefix := baseStyle.
  612. Foreground(t.TextMuted()).
  613. Render(" └ ")
  614. formattedParams := baseStyle.
  615. Width(width - 2 - lipgloss.Width(toolNameText)).
  616. Foreground(t.TextMuted()).
  617. Render(params)
  618. parts = append(parts, lipgloss.JoinHorizontal(lipgloss.Left, prefix, toolNameText, formattedParams))
  619. }
  620. // if toolCall.Name == agent.AgentToolName {
  621. // taskMessages, _ := messagesService.List(context.Background(), toolCall.ID)
  622. // toolCalls := []message.ToolCall{}
  623. // for _, v := range taskMessages {
  624. // toolCalls = append(toolCalls, v.ToolCalls()...)
  625. // }
  626. // for _, call := range toolCalls {
  627. // rendered := renderToolMessage(call, []message.Message{}, messagesService, focusedUIMessageId, true, width, 0)
  628. // parts = append(parts, rendered.content)
  629. // }
  630. // }
  631. if responseContent != "" && !nested {
  632. parts = append(parts, responseContent)
  633. }
  634. content := style.Render(
  635. lipgloss.JoinVertical(
  636. lipgloss.Left,
  637. parts...,
  638. ),
  639. )
  640. if nested {
  641. content = lipgloss.JoinVertical(
  642. lipgloss.Left,
  643. parts...,
  644. )
  645. }
  646. return content
  647. }