message.go 20 KB

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