prompt.go 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  1. package app
  2. import (
  3. "errors"
  4. "time"
  5. "github.com/sst/opencode-sdk-go"
  6. "github.com/sst/opencode/internal/attachment"
  7. "github.com/sst/opencode/internal/id"
  8. )
  9. type Prompt struct {
  10. Text string `toml:"text"`
  11. Attachments []*attachment.Attachment `toml:"attachments"`
  12. }
  13. func (p Prompt) ToMessage(
  14. messageID string,
  15. sessionID string,
  16. ) Message {
  17. message := opencode.UserMessage{
  18. ID: messageID,
  19. SessionID: sessionID,
  20. Role: opencode.UserMessageRoleUser,
  21. Time: opencode.UserMessageTime{
  22. Created: float64(time.Now().UnixMilli()),
  23. },
  24. }
  25. text := p.Text
  26. textAttachments := []*attachment.Attachment{}
  27. for _, attachment := range p.Attachments {
  28. if attachment.Type == "text" {
  29. textAttachments = append(textAttachments, attachment)
  30. }
  31. }
  32. for i := 0; i < len(textAttachments)-1; i++ {
  33. for j := i + 1; j < len(textAttachments); j++ {
  34. if textAttachments[i].StartIndex < textAttachments[j].StartIndex {
  35. textAttachments[i], textAttachments[j] = textAttachments[j], textAttachments[i]
  36. }
  37. }
  38. }
  39. for _, att := range textAttachments {
  40. if source, ok := att.GetTextSource(); ok {
  41. if att.StartIndex > att.EndIndex || att.EndIndex > len(text) {
  42. continue
  43. }
  44. text = text[:att.StartIndex] + source.Value + text[att.EndIndex:]
  45. }
  46. }
  47. parts := []opencode.PartUnion{opencode.TextPart{
  48. ID: id.Ascending(id.Part),
  49. MessageID: messageID,
  50. SessionID: sessionID,
  51. Type: opencode.TextPartTypeText,
  52. Text: text,
  53. }}
  54. for _, attachment := range p.Attachments {
  55. if attachment.Type == "agent" {
  56. source, _ := attachment.GetAgentSource()
  57. parts = append(parts, opencode.AgentPart{
  58. ID: id.Ascending(id.Part),
  59. MessageID: messageID,
  60. SessionID: sessionID,
  61. Name: source.Name,
  62. Source: opencode.AgentPartSource{
  63. Value: attachment.Display,
  64. Start: int64(attachment.StartIndex),
  65. End: int64(attachment.EndIndex),
  66. },
  67. })
  68. continue
  69. }
  70. text := opencode.FilePartSourceText{
  71. Start: int64(attachment.StartIndex),
  72. End: int64(attachment.EndIndex),
  73. Value: attachment.Display,
  74. }
  75. source := &opencode.FilePartSource{}
  76. switch attachment.Type {
  77. case "text":
  78. continue
  79. case "file":
  80. if fileSource, ok := attachment.GetFileSource(); ok {
  81. source = &opencode.FilePartSource{
  82. Text: text,
  83. Path: fileSource.Path,
  84. Type: opencode.FilePartSourceTypeFile,
  85. }
  86. }
  87. case "symbol":
  88. if symbolSource, ok := attachment.GetSymbolSource(); ok {
  89. source = &opencode.FilePartSource{
  90. Text: text,
  91. Path: symbolSource.Path,
  92. Type: opencode.FilePartSourceTypeSymbol,
  93. Kind: int64(symbolSource.Kind),
  94. Name: symbolSource.Name,
  95. Range: opencode.SymbolSourceRange{
  96. Start: opencode.SymbolSourceRangeStart{
  97. Line: float64(symbolSource.Range.Start.Line),
  98. Character: float64(symbolSource.Range.Start.Char),
  99. },
  100. End: opencode.SymbolSourceRangeEnd{
  101. Line: float64(symbolSource.Range.End.Line),
  102. Character: float64(symbolSource.Range.End.Char),
  103. },
  104. },
  105. }
  106. }
  107. }
  108. parts = append(parts, opencode.FilePart{
  109. ID: id.Ascending(id.Part),
  110. MessageID: messageID,
  111. SessionID: sessionID,
  112. Type: opencode.FilePartTypeFile,
  113. Filename: attachment.Filename,
  114. Mime: attachment.MediaType,
  115. URL: attachment.URL,
  116. Source: *source,
  117. })
  118. }
  119. return Message{
  120. Info: message,
  121. Parts: parts,
  122. }
  123. }
  124. func (m Message) ToPrompt() (*Prompt, error) {
  125. switch m.Info.(type) {
  126. case opencode.UserMessage:
  127. text := ""
  128. attachments := []*attachment.Attachment{}
  129. for _, part := range m.Parts {
  130. switch p := part.(type) {
  131. case opencode.TextPart:
  132. if p.Synthetic {
  133. continue
  134. }
  135. text += p.Text + " "
  136. case opencode.AgentPart:
  137. attachments = append(attachments, &attachment.Attachment{
  138. ID: p.ID,
  139. Type: "agent",
  140. Display: p.Source.Value,
  141. StartIndex: int(p.Source.Start),
  142. EndIndex: int(p.Source.End),
  143. Source: &attachment.AgentSource{
  144. Name: p.Name,
  145. },
  146. })
  147. case opencode.FilePart:
  148. switch p.Source.Type {
  149. case "file":
  150. attachments = append(attachments, &attachment.Attachment{
  151. ID: p.ID,
  152. Type: "file",
  153. Display: p.Source.Text.Value,
  154. URL: p.URL,
  155. Filename: p.Filename,
  156. MediaType: p.Mime,
  157. StartIndex: int(p.Source.Text.Start),
  158. EndIndex: int(p.Source.Text.End),
  159. Source: &attachment.FileSource{
  160. Path: p.Source.Path,
  161. Mime: p.Mime,
  162. },
  163. })
  164. case "symbol":
  165. r := p.Source.Range.(opencode.SymbolSourceRange)
  166. attachments = append(attachments, &attachment.Attachment{
  167. ID: p.ID,
  168. Type: "symbol",
  169. Display: p.Source.Text.Value,
  170. URL: p.URL,
  171. Filename: p.Filename,
  172. MediaType: p.Mime,
  173. StartIndex: int(p.Source.Text.Start),
  174. EndIndex: int(p.Source.Text.End),
  175. Source: &attachment.SymbolSource{
  176. Path: p.Source.Path,
  177. Name: p.Source.Name,
  178. Kind: int(p.Source.Kind),
  179. Range: attachment.SymbolRange{
  180. Start: attachment.Position{
  181. Line: int(r.Start.Line),
  182. Char: int(r.Start.Character),
  183. },
  184. End: attachment.Position{
  185. Line: int(r.End.Line),
  186. Char: int(r.End.Character),
  187. },
  188. },
  189. },
  190. })
  191. }
  192. }
  193. }
  194. return &Prompt{
  195. Text: text,
  196. Attachments: attachments,
  197. }, nil
  198. }
  199. return nil, errors.New("unknown message type")
  200. }
  201. func (m Message) ToSessionChatParams() []opencode.SessionPromptParamsPartUnion {
  202. parts := []opencode.SessionPromptParamsPartUnion{}
  203. for _, part := range m.Parts {
  204. switch p := part.(type) {
  205. case opencode.TextPart:
  206. parts = append(parts, opencode.TextPartInputParam{
  207. ID: opencode.F(p.ID),
  208. Type: opencode.F(opencode.TextPartInputTypeText),
  209. Text: opencode.F(p.Text),
  210. Synthetic: opencode.F(p.Synthetic),
  211. Time: opencode.F(opencode.TextPartInputTimeParam{
  212. Start: opencode.F(p.Time.Start),
  213. End: opencode.F(p.Time.End),
  214. }),
  215. })
  216. case opencode.FilePart:
  217. var source opencode.FilePartSourceUnionParam
  218. switch p.Source.Type {
  219. case "file":
  220. source = opencode.FileSourceParam{
  221. Type: opencode.F(opencode.FileSourceTypeFile),
  222. Path: opencode.F(p.Source.Path),
  223. Text: opencode.F(opencode.FilePartSourceTextParam{
  224. Start: opencode.F(int64(p.Source.Text.Start)),
  225. End: opencode.F(int64(p.Source.Text.End)),
  226. Value: opencode.F(p.Source.Text.Value),
  227. }),
  228. }
  229. case "symbol":
  230. source = opencode.SymbolSourceParam{
  231. Type: opencode.F(opencode.SymbolSourceTypeSymbol),
  232. Path: opencode.F(p.Source.Path),
  233. Name: opencode.F(p.Source.Name),
  234. Kind: opencode.F(p.Source.Kind),
  235. Range: opencode.F(opencode.SymbolSourceRangeParam{
  236. Start: opencode.F(opencode.SymbolSourceRangeStartParam{
  237. Line: opencode.F(float64(p.Source.Range.(opencode.SymbolSourceRange).Start.Line)),
  238. Character: opencode.F(float64(p.Source.Range.(opencode.SymbolSourceRange).Start.Character)),
  239. }),
  240. End: opencode.F(opencode.SymbolSourceRangeEndParam{
  241. Line: opencode.F(float64(p.Source.Range.(opencode.SymbolSourceRange).End.Line)),
  242. Character: opencode.F(float64(p.Source.Range.(opencode.SymbolSourceRange).End.Character)),
  243. }),
  244. }),
  245. Text: opencode.F(opencode.FilePartSourceTextParam{
  246. Value: opencode.F(p.Source.Text.Value),
  247. Start: opencode.F(p.Source.Text.Start),
  248. End: opencode.F(p.Source.Text.End),
  249. }),
  250. }
  251. }
  252. parts = append(parts, opencode.FilePartInputParam{
  253. ID: opencode.F(p.ID),
  254. Type: opencode.F(opencode.FilePartInputTypeFile),
  255. Mime: opencode.F(p.Mime),
  256. URL: opencode.F(p.URL),
  257. Filename: opencode.F(p.Filename),
  258. Source: opencode.F(source),
  259. })
  260. case opencode.AgentPart:
  261. parts = append(parts, opencode.AgentPartInputParam{
  262. ID: opencode.F(p.ID),
  263. Type: opencode.F(opencode.AgentPartInputTypeAgent),
  264. Name: opencode.F(p.Name),
  265. Source: opencode.F(opencode.AgentPartInputSourceParam{
  266. Value: opencode.F(p.Source.Value),
  267. Start: opencode.F(p.Source.Start),
  268. End: opencode.F(p.Source.End),
  269. }),
  270. })
  271. }
  272. }
  273. return parts
  274. }