fetch.go 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. package chat
  2. import (
  3. "encoding/json"
  4. "github.com/charmbracelet/crush/internal/agent/tools"
  5. "github.com/charmbracelet/crush/internal/message"
  6. "github.com/charmbracelet/crush/internal/ui/styles"
  7. )
  8. // -----------------------------------------------------------------------------
  9. // Fetch Tool
  10. // -----------------------------------------------------------------------------
  11. // FetchToolMessageItem is a message item that represents a fetch tool call.
  12. type FetchToolMessageItem struct {
  13. *baseToolMessageItem
  14. }
  15. var _ ToolMessageItem = (*FetchToolMessageItem)(nil)
  16. // NewFetchToolMessageItem creates a new [FetchToolMessageItem].
  17. func NewFetchToolMessageItem(
  18. sty *styles.Styles,
  19. toolCall message.ToolCall,
  20. result *message.ToolResult,
  21. canceled bool,
  22. ) ToolMessageItem {
  23. return newBaseToolMessageItem(sty, toolCall, result, &FetchToolRenderContext{}, canceled)
  24. }
  25. // FetchToolRenderContext renders fetch tool messages.
  26. type FetchToolRenderContext struct{}
  27. // RenderTool implements the [ToolRenderer] interface.
  28. func (f *FetchToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string {
  29. cappedWidth := cappedMessageWidth(width)
  30. if opts.IsPending() {
  31. return pendingTool(sty, "Fetch", opts.Anim, opts.Compact)
  32. }
  33. var params tools.FetchParams
  34. if err := json.Unmarshal([]byte(opts.ToolCall.Input), &params); err != nil {
  35. return toolErrorContent(sty, &message.ToolResult{Content: "Invalid parameters"}, cappedWidth)
  36. }
  37. toolParams := []string{params.URL}
  38. if params.Format != "" {
  39. toolParams = append(toolParams, "format", params.Format)
  40. }
  41. if params.Timeout != 0 {
  42. toolParams = append(toolParams, "timeout", formatTimeout(params.Timeout))
  43. }
  44. header := toolHeader(sty, opts.Status, "Fetch", cappedWidth, opts.Compact, toolParams...)
  45. if opts.Compact {
  46. return header
  47. }
  48. if earlyState, ok := toolEarlyStateContent(sty, opts, cappedWidth); ok {
  49. return joinToolParts(header, earlyState)
  50. }
  51. if opts.HasEmptyResult() {
  52. return header
  53. }
  54. // Determine file extension for syntax highlighting based on format.
  55. file := getFileExtensionForFormat(params.Format)
  56. body := toolOutputCodeContent(sty, file, opts.Result.Content, 0, cappedWidth, opts.ExpandedContent)
  57. return joinToolParts(header, body)
  58. }
  59. // getFileExtensionForFormat returns a filename with appropriate extension for syntax highlighting.
  60. func getFileExtensionForFormat(format string) string {
  61. switch format {
  62. case "text":
  63. return "fetch.txt"
  64. case "html":
  65. return "fetch.html"
  66. default:
  67. return "fetch.md"
  68. }
  69. }
  70. // -----------------------------------------------------------------------------
  71. // WebFetch Tool
  72. // -----------------------------------------------------------------------------
  73. // WebFetchToolMessageItem is a message item that represents a web_fetch tool call.
  74. type WebFetchToolMessageItem struct {
  75. *baseToolMessageItem
  76. }
  77. var _ ToolMessageItem = (*WebFetchToolMessageItem)(nil)
  78. // NewWebFetchToolMessageItem creates a new [WebFetchToolMessageItem].
  79. func NewWebFetchToolMessageItem(
  80. sty *styles.Styles,
  81. toolCall message.ToolCall,
  82. result *message.ToolResult,
  83. canceled bool,
  84. ) ToolMessageItem {
  85. return newBaseToolMessageItem(sty, toolCall, result, &WebFetchToolRenderContext{}, canceled)
  86. }
  87. // WebFetchToolRenderContext renders web_fetch tool messages.
  88. type WebFetchToolRenderContext struct{}
  89. // RenderTool implements the [ToolRenderer] interface.
  90. func (w *WebFetchToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string {
  91. cappedWidth := cappedMessageWidth(width)
  92. if opts.IsPending() {
  93. return pendingTool(sty, "Fetch", opts.Anim, opts.Compact)
  94. }
  95. var params tools.WebFetchParams
  96. if err := json.Unmarshal([]byte(opts.ToolCall.Input), &params); err != nil {
  97. return toolErrorContent(sty, &message.ToolResult{Content: "Invalid parameters"}, cappedWidth)
  98. }
  99. toolParams := []string{params.URL}
  100. header := toolHeader(sty, opts.Status, "Fetch", cappedWidth, opts.Compact, toolParams...)
  101. if opts.Compact {
  102. return header
  103. }
  104. if earlyState, ok := toolEarlyStateContent(sty, opts, cappedWidth); ok {
  105. return joinToolParts(header, earlyState)
  106. }
  107. if opts.HasEmptyResult() {
  108. return header
  109. }
  110. body := toolOutputMarkdownContent(sty, opts.Result.Content, cappedWidth, opts.ExpandedContent)
  111. return joinToolParts(header, body)
  112. }
  113. // -----------------------------------------------------------------------------
  114. // WebSearch Tool
  115. // -----------------------------------------------------------------------------
  116. // WebSearchToolMessageItem is a message item that represents a web_search tool call.
  117. type WebSearchToolMessageItem struct {
  118. *baseToolMessageItem
  119. }
  120. var _ ToolMessageItem = (*WebSearchToolMessageItem)(nil)
  121. // NewWebSearchToolMessageItem creates a new [WebSearchToolMessageItem].
  122. func NewWebSearchToolMessageItem(
  123. sty *styles.Styles,
  124. toolCall message.ToolCall,
  125. result *message.ToolResult,
  126. canceled bool,
  127. ) ToolMessageItem {
  128. return newBaseToolMessageItem(sty, toolCall, result, &WebSearchToolRenderContext{}, canceled)
  129. }
  130. // WebSearchToolRenderContext renders web_search tool messages.
  131. type WebSearchToolRenderContext struct{}
  132. // RenderTool implements the [ToolRenderer] interface.
  133. func (w *WebSearchToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string {
  134. cappedWidth := cappedMessageWidth(width)
  135. if opts.IsPending() {
  136. return pendingTool(sty, "Search", opts.Anim, opts.Compact)
  137. }
  138. var params tools.WebSearchParams
  139. if err := json.Unmarshal([]byte(opts.ToolCall.Input), &params); err != nil {
  140. return toolErrorContent(sty, &message.ToolResult{Content: "Invalid parameters"}, cappedWidth)
  141. }
  142. toolParams := []string{params.Query}
  143. header := toolHeader(sty, opts.Status, "Search", cappedWidth, opts.Compact, toolParams...)
  144. if opts.Compact {
  145. return header
  146. }
  147. if earlyState, ok := toolEarlyStateContent(sty, opts, cappedWidth); ok {
  148. return joinToolParts(header, earlyState)
  149. }
  150. if opts.HasEmptyResult() {
  151. return header
  152. }
  153. body := toolOutputMarkdownContent(sty, opts.Result.Content, cappedWidth, opts.ExpandedContent)
  154. return joinToolParts(header, body)
  155. }