run.ts 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  1. import type { Argv } from "yargs"
  2. import { Bus } from "../../bus"
  3. import { Provider } from "../../provider/provider"
  4. import { Session } from "../../session"
  5. import { UI } from "../ui"
  6. import { cmd } from "./cmd"
  7. import { Flag } from "../../flag/flag"
  8. import { Config } from "../../config/config"
  9. import { bootstrap } from "../bootstrap"
  10. import { MessageV2 } from "../../session/message-v2"
  11. const TOOL: Record<string, [string, string]> = {
  12. todowrite: ["Todo", UI.Style.TEXT_WARNING_BOLD],
  13. todoread: ["Todo", UI.Style.TEXT_WARNING_BOLD],
  14. bash: ["Bash", UI.Style.TEXT_DANGER_BOLD],
  15. edit: ["Edit", UI.Style.TEXT_SUCCESS_BOLD],
  16. glob: ["Glob", UI.Style.TEXT_INFO_BOLD],
  17. grep: ["Grep", UI.Style.TEXT_INFO_BOLD],
  18. list: ["List", UI.Style.TEXT_INFO_BOLD],
  19. read: ["Read", UI.Style.TEXT_HIGHLIGHT_BOLD],
  20. write: ["Write", UI.Style.TEXT_SUCCESS_BOLD],
  21. websearch: ["Search", UI.Style.TEXT_DIM_BOLD],
  22. }
  23. export const RunCommand = cmd({
  24. command: "run [message..]",
  25. describe: "run opencode with a message",
  26. builder: (yargs: Argv) => {
  27. return yargs
  28. .positional("message", {
  29. describe: "message to send",
  30. type: "string",
  31. array: true,
  32. default: [],
  33. })
  34. .option("continue", {
  35. alias: ["c"],
  36. describe: "continue the last session",
  37. type: "boolean",
  38. })
  39. .option("session", {
  40. alias: ["s"],
  41. describe: "session id to continue",
  42. type: "string",
  43. })
  44. .option("share", {
  45. type: "boolean",
  46. describe: "share the session",
  47. })
  48. .option("model", {
  49. type: "string",
  50. alias: ["m"],
  51. describe: "model to use in the format of provider/model",
  52. })
  53. },
  54. handler: async (args) => {
  55. let message = args.message.join(" ")
  56. if (!process.stdin.isTTY) message += "\n" + (await Bun.stdin.text())
  57. await bootstrap({ cwd: process.cwd() }, async () => {
  58. const session = await (async () => {
  59. if (args.continue) {
  60. const first = await Session.list().next()
  61. if (first.done) return
  62. return first.value
  63. }
  64. if (args.session) return Session.get(args.session)
  65. return Session.create()
  66. })()
  67. if (!session) {
  68. UI.error("Session not found")
  69. return
  70. }
  71. const isPiped = !process.stdout.isTTY
  72. UI.empty()
  73. UI.println(UI.logo())
  74. UI.empty()
  75. const displayMessage = message.length > 300 ? message.slice(0, 300) + "..." : message
  76. UI.println(UI.Style.TEXT_NORMAL_BOLD + "> ", displayMessage)
  77. UI.empty()
  78. const cfg = await Config.get()
  79. if (cfg.autoshare || Flag.OPENCODE_AUTO_SHARE || args.share) {
  80. await Session.share(session.id)
  81. UI.println(UI.Style.TEXT_INFO_BOLD + "~ https://opencode.ai/s/" + session.id.slice(-8))
  82. }
  83. UI.empty()
  84. const { providerID, modelID } = args.model ? Provider.parseModel(args.model) : await Provider.defaultModel()
  85. UI.println(UI.Style.TEXT_NORMAL_BOLD + "@ ", UI.Style.TEXT_NORMAL + `${providerID}/${modelID}`)
  86. UI.empty()
  87. function printEvent(color: string, type: string, title: string) {
  88. UI.println(
  89. color + `|`,
  90. UI.Style.TEXT_NORMAL + UI.Style.TEXT_DIM + ` ${type.padEnd(7, " ")}`,
  91. "",
  92. UI.Style.TEXT_NORMAL + title,
  93. )
  94. }
  95. Bus.subscribe(MessageV2.Event.PartUpdated, async (evt) => {
  96. if (evt.properties.sessionID !== session.id) return
  97. const part = evt.properties.part
  98. if (part.type === "tool" && part.state.status === "completed") {
  99. const [tool, color] = TOOL[part.tool] ?? [part.tool, UI.Style.TEXT_INFO_BOLD]
  100. printEvent(color, tool, part.state.title || "Unknown")
  101. }
  102. if (part.type === "text") {
  103. if (part.text.includes("\n")) {
  104. UI.empty()
  105. UI.println(part.text)
  106. UI.empty()
  107. return
  108. }
  109. printEvent(UI.Style.TEXT_NORMAL_BOLD, "Text", part.text)
  110. }
  111. })
  112. const result = await Session.chat({
  113. sessionID: session.id,
  114. providerID,
  115. modelID,
  116. parts: [
  117. {
  118. type: "text",
  119. text: message,
  120. },
  121. ],
  122. })
  123. if (isPiped) {
  124. const match = result.parts.findLast((x) => x.type === "text")
  125. if (match) process.stdout.write(match.text)
  126. }
  127. UI.empty()
  128. })
  129. },
  130. })