Bladeren bron

chore: generate

opencode-agent[bot] 4 maanden geleden
bovenliggende
commit
61150f6391
1 gewijzigde bestanden met toevoegingen van 303 en 303 verwijderingen
  1. 303 303
      packages/opencode/src/cli/cmd/run.ts

+ 303 - 303
packages/opencode/src/cli/cmd/run.ts

@@ -300,288 +300,310 @@ export const RunCommand = effectCmd({
       }),
   handler: Effect.fn("Cli.run")(function* (args) {
     yield* Effect.promise(async () => {
-    let message = [...args.message, ...(args["--"] || [])]
-      .map((arg) => (arg.includes(" ") ? `"${arg.replace(/"/g, '\\"')}"` : arg))
-      .join(" ")
-
-    const directory = (() => {
-      if (!args.dir) return undefined
-      if (args.attach) return args.dir
-      try {
-        process.chdir(args.dir)
-        return process.cwd()
-      } catch {
-        UI.error("Failed to change directory to " + args.dir)
-        process.exit(1)
-      }
-    })()
-
-    const files: { type: "file"; url: string; filename: string; mime: string }[] = []
-    if (args.file) {
-      const list = Array.isArray(args.file) ? args.file : [args.file]
+      let message = [...args.message, ...(args["--"] || [])]
+        .map((arg) => (arg.includes(" ") ? `"${arg.replace(/"/g, '\\"')}"` : arg))
+        .join(" ")
 
-      for (const filePath of list) {
-        const resolvedPath = path.resolve(process.cwd(), filePath)
-        if (!(await Filesystem.exists(resolvedPath))) {
-          UI.error(`File not found: ${filePath}`)
+      const directory = (() => {
+        if (!args.dir) return undefined
+        if (args.attach) return args.dir
+        try {
+          process.chdir(args.dir)
+          return process.cwd()
+        } catch {
+          UI.error("Failed to change directory to " + args.dir)
           process.exit(1)
         }
+      })()
 
-        const mime = (await Filesystem.isDir(resolvedPath)) ? "application/x-directory" : "text/plain"
+      const files: { type: "file"; url: string; filename: string; mime: string }[] = []
+      if (args.file) {
+        const list = Array.isArray(args.file) ? args.file : [args.file]
 
-        files.push({
-          type: "file",
-          url: pathToFileURL(resolvedPath).href,
-          filename: path.basename(resolvedPath),
-          mime,
-        })
+        for (const filePath of list) {
+          const resolvedPath = path.resolve(process.cwd(), filePath)
+          if (!(await Filesystem.exists(resolvedPath))) {
+            UI.error(`File not found: ${filePath}`)
+            process.exit(1)
+          }
+
+          const mime = (await Filesystem.isDir(resolvedPath)) ? "application/x-directory" : "text/plain"
+
+          files.push({
+            type: "file",
+            url: pathToFileURL(resolvedPath).href,
+            filename: path.basename(resolvedPath),
+            mime,
+          })
+        }
       }
-    }
-
-    if (!process.stdin.isTTY) message += "\n" + (await Bun.stdin.text())
-
-    if (message.trim().length === 0 && !args.command) {
-      UI.error("You must provide a message or a command")
-      process.exit(1)
-    }
-
-    if (args.fork && !args.continue && !args.session) {
-      UI.error("--fork requires --continue or --session")
-      process.exit(1)
-    }
-
-    const rules: Permission.Ruleset = [
-      {
-        permission: "question",
-        action: "deny",
-        pattern: "*",
-      },
-      {
-        permission: "plan_enter",
-        action: "deny",
-        pattern: "*",
-      },
-      {
-        permission: "plan_exit",
-        action: "deny",
-        pattern: "*",
-      },
-    ]
-
-    function title() {
-      if (args.title === undefined) return
-      if (args.title !== "") return args.title
-      return message.slice(0, 50) + (message.length > 50 ? "..." : "")
-    }
-
-    async function session(sdk: OpencodeClient) {
-      const baseID = args.continue ? (await sdk.session.list()).data?.find((s) => !s.parentID)?.id : args.session
-
-      if (baseID && args.fork) {
-        const forked = await sdk.session.fork({ sessionID: baseID })
-        return forked.data?.id
+
+      if (!process.stdin.isTTY) message += "\n" + (await Bun.stdin.text())
+
+      if (message.trim().length === 0 && !args.command) {
+        UI.error("You must provide a message or a command")
+        process.exit(1)
       }
 
-      if (baseID) return baseID
+      if (args.fork && !args.continue && !args.session) {
+        UI.error("--fork requires --continue or --session")
+        process.exit(1)
+      }
 
-      const name = title()
-      const result = await sdk.session.create({ title: name, permission: rules })
-      return result.data?.id
-    }
+      const rules: Permission.Ruleset = [
+        {
+          permission: "question",
+          action: "deny",
+          pattern: "*",
+        },
+        {
+          permission: "plan_enter",
+          action: "deny",
+          pattern: "*",
+        },
+        {
+          permission: "plan_exit",
+          action: "deny",
+          pattern: "*",
+        },
+      ]
+
+      function title() {
+        if (args.title === undefined) return
+        if (args.title !== "") return args.title
+        return message.slice(0, 50) + (message.length > 50 ? "..." : "")
+      }
 
-    async function share(sdk: OpencodeClient, sessionID: string) {
-      const cfg = await sdk.config.get()
-      if (!cfg.data) return
-      if (cfg.data.share !== "auto" && !Flag.OPENCODE_AUTO_SHARE && !args.share) return
-      const res = await sdk.session.share({ sessionID }).catch((error) => {
-        if (error instanceof Error && error.message.includes("disabled")) {
-          UI.println(UI.Style.TEXT_DANGER_BOLD + "!  " + error.message)
+      async function session(sdk: OpencodeClient) {
+        const baseID = args.continue ? (await sdk.session.list()).data?.find((s) => !s.parentID)?.id : args.session
+
+        if (baseID && args.fork) {
+          const forked = await sdk.session.fork({ sessionID: baseID })
+          return forked.data?.id
         }
-        return { error }
-      })
-      if (!res.error && "data" in res && res.data?.share?.url) {
-        UI.println(UI.Style.TEXT_INFO_BOLD + "~  " + res.data.share.url)
+
+        if (baseID) return baseID
+
+        const name = title()
+        const result = await sdk.session.create({ title: name, permission: rules })
+        return result.data?.id
       }
-    }
 
-    async function execute(sdk: OpencodeClient) {
-      function tool(part: ToolPart) {
-        try {
-          if (part.tool === ShellID.ToolID) return shell(props<typeof ShellTool>(part))
-          if (part.tool === "glob") return glob(props<typeof GlobTool>(part))
-          if (part.tool === "grep") return grep(props<typeof GrepTool>(part))
-          if (part.tool === "read") return read(props<typeof ReadTool>(part))
-          if (part.tool === "write") return write(props<typeof WriteTool>(part))
-          if (part.tool === "webfetch") return webfetch(props<typeof WebFetchTool>(part))
-          if (part.tool === "edit") return edit(props<typeof EditTool>(part))
-          if (part.tool === "websearch") return websearch(props<typeof WebSearchTool>(part))
-          if (part.tool === "task") return task(props<typeof TaskTool>(part))
-          if (part.tool === "todowrite") return todo(props<typeof TodoWriteTool>(part))
-          if (part.tool === "skill") return skill(props<typeof SkillTool>(part))
-          return fallback(part)
-        } catch {
-          return fallback(part)
+      async function share(sdk: OpencodeClient, sessionID: string) {
+        const cfg = await sdk.config.get()
+        if (!cfg.data) return
+        if (cfg.data.share !== "auto" && !Flag.OPENCODE_AUTO_SHARE && !args.share) return
+        const res = await sdk.session.share({ sessionID }).catch((error) => {
+          if (error instanceof Error && error.message.includes("disabled")) {
+            UI.println(UI.Style.TEXT_DANGER_BOLD + "!  " + error.message)
+          }
+          return { error }
+        })
+        if (!res.error && "data" in res && res.data?.share?.url) {
+          UI.println(UI.Style.TEXT_INFO_BOLD + "~  " + res.data.share.url)
         }
       }
 
-      function emit(type: string, data: Record<string, unknown>) {
-        if (args.format === "json") {
-          process.stdout.write(JSON.stringify({ type, timestamp: Date.now(), sessionID, ...data }) + EOL)
-          return true
+      async function execute(sdk: OpencodeClient) {
+        function tool(part: ToolPart) {
+          try {
+            if (part.tool === ShellID.ToolID) return shell(props<typeof ShellTool>(part))
+            if (part.tool === "glob") return glob(props<typeof GlobTool>(part))
+            if (part.tool === "grep") return grep(props<typeof GrepTool>(part))
+            if (part.tool === "read") return read(props<typeof ReadTool>(part))
+            if (part.tool === "write") return write(props<typeof WriteTool>(part))
+            if (part.tool === "webfetch") return webfetch(props<typeof WebFetchTool>(part))
+            if (part.tool === "edit") return edit(props<typeof EditTool>(part))
+            if (part.tool === "websearch") return websearch(props<typeof WebSearchTool>(part))
+            if (part.tool === "task") return task(props<typeof TaskTool>(part))
+            if (part.tool === "todowrite") return todo(props<typeof TodoWriteTool>(part))
+            if (part.tool === "skill") return skill(props<typeof SkillTool>(part))
+            return fallback(part)
+          } catch {
+            return fallback(part)
+          }
         }
-        return false
-      }
 
-      const events = await sdk.event.subscribe()
-      let error: string | undefined
-
-      async function loop() {
-        const toggles = new Map<string, boolean>()
-
-        for await (const event of events.stream) {
-          if (
-            event.type === "message.updated" &&
-            event.properties.info.role === "assistant" &&
-            args.format !== "json" &&
-            toggles.get("start") !== true
-          ) {
-            UI.empty()
-            UI.println(`> ${event.properties.info.agent} · ${event.properties.info.modelID}`)
-            UI.empty()
-            toggles.set("start", true)
+        function emit(type: string, data: Record<string, unknown>) {
+          if (args.format === "json") {
+            process.stdout.write(JSON.stringify({ type, timestamp: Date.now(), sessionID, ...data }) + EOL)
+            return true
           }
+          return false
+        }
 
-          if (event.type === "message.part.updated") {
-            const part = event.properties.part
-            if (part.sessionID !== sessionID) continue
+        const events = await sdk.event.subscribe()
+        let error: string | undefined
 
-            if (part.type === "tool" && (part.state.status === "completed" || part.state.status === "error")) {
-              if (emit("tool_use", { part })) continue
-              if (part.state.status === "completed") {
-                tool(part)
-                continue
-              }
-              inline({
-                icon: "✗",
-                title: `${part.tool} failed`,
-              })
-              UI.error(part.state.error)
-            }
+        async function loop() {
+          const toggles = new Map<string, boolean>()
 
+          for await (const event of events.stream) {
             if (
-              part.type === "tool" &&
-              part.tool === "task" &&
-              part.state.status === "running" &&
-              args.format !== "json"
+              event.type === "message.updated" &&
+              event.properties.info.role === "assistant" &&
+              args.format !== "json" &&
+              toggles.get("start") !== true
             ) {
-              if (toggles.get(part.id) === true) continue
-              task(props<typeof TaskTool>(part))
-              toggles.set(part.id, true)
+              UI.empty()
+              UI.println(`> ${event.properties.info.agent} · ${event.properties.info.modelID}`)
+              UI.empty()
+              toggles.set("start", true)
             }
 
-            if (part.type === "step-start") {
-              if (emit("step_start", { part })) continue
-            }
+            if (event.type === "message.part.updated") {
+              const part = event.properties.part
+              if (part.sessionID !== sessionID) continue
+
+              if (part.type === "tool" && (part.state.status === "completed" || part.state.status === "error")) {
+                if (emit("tool_use", { part })) continue
+                if (part.state.status === "completed") {
+                  tool(part)
+                  continue
+                }
+                inline({
+                  icon: "✗",
+                  title: `${part.tool} failed`,
+                })
+                UI.error(part.state.error)
+              }
 
-            if (part.type === "step-finish") {
-              if (emit("step_finish", { part })) continue
-            }
+              if (
+                part.type === "tool" &&
+                part.tool === "task" &&
+                part.state.status === "running" &&
+                args.format !== "json"
+              ) {
+                if (toggles.get(part.id) === true) continue
+                task(props<typeof TaskTool>(part))
+                toggles.set(part.id, true)
+              }
 
-            if (part.type === "text" && part.time?.end) {
-              if (emit("text", { part })) continue
-              const text = part.text.trim()
-              if (!text) continue
-              if (!process.stdout.isTTY) {
-                process.stdout.write(text + EOL)
-                continue
+              if (part.type === "step-start") {
+                if (emit("step_start", { part })) continue
               }
-              UI.empty()
-              UI.println(text)
-              UI.empty()
-            }
 
-            if (part.type === "reasoning" && part.time?.end && args.thinking) {
-              if (emit("reasoning", { part })) continue
-              const text = part.text.trim()
-              if (!text) continue
-              const line = `Thinking: ${text}`
-              if (process.stdout.isTTY) {
+              if (part.type === "step-finish") {
+                if (emit("step_finish", { part })) continue
+              }
+
+              if (part.type === "text" && part.time?.end) {
+                if (emit("text", { part })) continue
+                const text = part.text.trim()
+                if (!text) continue
+                if (!process.stdout.isTTY) {
+                  process.stdout.write(text + EOL)
+                  continue
+                }
                 UI.empty()
-                UI.println(`${UI.Style.TEXT_DIM}\u001b[3m${line}\u001b[0m${UI.Style.TEXT_NORMAL}`)
+                UI.println(text)
                 UI.empty()
-                continue
               }
-              process.stdout.write(line + EOL)
+
+              if (part.type === "reasoning" && part.time?.end && args.thinking) {
+                if (emit("reasoning", { part })) continue
+                const text = part.text.trim()
+                if (!text) continue
+                const line = `Thinking: ${text}`
+                if (process.stdout.isTTY) {
+                  UI.empty()
+                  UI.println(`${UI.Style.TEXT_DIM}\u001b[3m${line}\u001b[0m${UI.Style.TEXT_NORMAL}`)
+                  UI.empty()
+                  continue
+                }
+                process.stdout.write(line + EOL)
+              }
             }
-          }
 
-          if (event.type === "session.error") {
-            const props = event.properties
-            if (props.sessionID !== sessionID || !props.error) continue
-            let err = String(props.error.name)
-            if ("data" in props.error && props.error.data && "message" in props.error.data) {
-              err = String(props.error.data.message)
+            if (event.type === "session.error") {
+              const props = event.properties
+              if (props.sessionID !== sessionID || !props.error) continue
+              let err = String(props.error.name)
+              if ("data" in props.error && props.error.data && "message" in props.error.data) {
+                err = String(props.error.data.message)
+              }
+              error = error ? error + EOL + err : err
+              if (emit("error", { error: props.error })) continue
+              UI.error(err)
+            }
+
+            if (
+              event.type === "session.status" &&
+              event.properties.sessionID === sessionID &&
+              event.properties.status.type === "idle"
+            ) {
+              break
             }
-            error = error ? error + EOL + err : err
-            if (emit("error", { error: props.error })) continue
-            UI.error(err)
-          }
 
-          if (
-            event.type === "session.status" &&
-            event.properties.sessionID === sessionID &&
-            event.properties.status.type === "idle"
-          ) {
-            break
+            if (event.type === "permission.asked") {
+              const permission = event.properties
+              if (permission.sessionID !== sessionID) continue
+
+              if (args["dangerously-skip-permissions"]) {
+                await sdk.permission.reply({
+                  requestID: permission.id,
+                  reply: "once",
+                })
+              } else {
+                UI.println(
+                  UI.Style.TEXT_WARNING_BOLD + "!",
+                  UI.Style.TEXT_NORMAL +
+                    `permission requested: ${permission.permission} (${permission.patterns.join(", ")}); auto-rejecting`,
+                )
+                await sdk.permission.reply({
+                  requestID: permission.id,
+                  reply: "reject",
+                })
+              }
+            }
           }
+        }
 
-          if (event.type === "permission.asked") {
-            const permission = event.properties
-            if (permission.sessionID !== sessionID) continue
+        // Validate agent if specified
+        const agent = await (async () => {
+          if (!args.agent) return undefined
+          const name = args.agent
 
-            if (args["dangerously-skip-permissions"]) {
-              await sdk.permission.reply({
-                requestID: permission.id,
-                reply: "once",
-              })
-            } else {
+          // When attaching, validate against the running server instead of local Instance state.
+          if (args.attach) {
+            const modes = await sdk.app
+              .agents(undefined, { throwOnError: true })
+              .then((x) => x.data ?? [])
+              .catch(() => undefined)
+
+            if (!modes) {
               UI.println(
                 UI.Style.TEXT_WARNING_BOLD + "!",
-                UI.Style.TEXT_NORMAL +
-                  `permission requested: ${permission.permission} (${permission.patterns.join(", ")}); auto-rejecting`,
+                UI.Style.TEXT_NORMAL,
+                `failed to list agents from ${args.attach}. Falling back to default agent`,
               )
-              await sdk.permission.reply({
-                requestID: permission.id,
-                reply: "reject",
-              })
+              return undefined
             }
-          }
-        }
-      }
 
-      // Validate agent if specified
-      const agent = await (async () => {
-        if (!args.agent) return undefined
-        const name = args.agent
+            const agent = modes.find((a) => a.name === name)
+            if (!agent) {
+              UI.println(
+                UI.Style.TEXT_WARNING_BOLD + "!",
+                UI.Style.TEXT_NORMAL,
+                `agent "${name}" not found. Falling back to default agent`,
+              )
+              return undefined
+            }
 
-        // When attaching, validate against the running server instead of local Instance state.
-        if (args.attach) {
-          const modes = await sdk.app
-            .agents(undefined, { throwOnError: true })
-            .then((x) => x.data ?? [])
-            .catch(() => undefined)
+            if (agent.mode === "subagent") {
+              UI.println(
+                UI.Style.TEXT_WARNING_BOLD + "!",
+                UI.Style.TEXT_NORMAL,
+                `agent "${name}" is a subagent, not a primary agent. Falling back to default agent`,
+              )
+              return undefined
+            }
 
-          if (!modes) {
-            UI.println(
-              UI.Style.TEXT_WARNING_BOLD + "!",
-              UI.Style.TEXT_NORMAL,
-              `failed to list agents from ${args.attach}. Falling back to default agent`,
-            )
-            return undefined
+            return name
           }
 
-          const agent = modes.find((a) => a.name === name)
-          if (!agent) {
+          const entry = await AppRuntime.runPromise(Agent.Service.use((svc) => svc.get(name)))
+          if (!entry) {
             UI.println(
               UI.Style.TEXT_WARNING_BOLD + "!",
               UI.Style.TEXT_NORMAL,
@@ -589,8 +611,7 @@ export const RunCommand = effectCmd({
             )
             return undefined
           }
-
-          if (agent.mode === "subagent") {
+          if (entry.mode === "subagent") {
             UI.println(
               UI.Style.TEXT_WARNING_BOLD + "!",
               UI.Style.TEXT_NORMAL,
@@ -598,81 +619,60 @@ export const RunCommand = effectCmd({
             )
             return undefined
           }
-
           return name
-        }
+        })()
 
-        const entry = await AppRuntime.runPromise(Agent.Service.use((svc) => svc.get(name)))
-        if (!entry) {
-          UI.println(
-            UI.Style.TEXT_WARNING_BOLD + "!",
-            UI.Style.TEXT_NORMAL,
-            `agent "${name}" not found. Falling back to default agent`,
-          )
-          return undefined
-        }
-        if (entry.mode === "subagent") {
-          UI.println(
-            UI.Style.TEXT_WARNING_BOLD + "!",
-            UI.Style.TEXT_NORMAL,
-            `agent "${name}" is a subagent, not a primary agent. Falling back to default agent`,
-          )
-          return undefined
+        const sessionID = await session(sdk)
+        if (!sessionID) {
+          UI.error("Session not found")
+          process.exit(1)
         }
-        return name
-      })()
+        await share(sdk, sessionID)
 
-      const sessionID = await session(sdk)
-      if (!sessionID) {
-        UI.error("Session not found")
-        process.exit(1)
-      }
-      await share(sdk, sessionID)
+        loop().catch((e) => {
+          console.error(e)
+          process.exit(1)
+        })
 
-      loop().catch((e) => {
-        console.error(e)
-        process.exit(1)
-      })
+        if (args.command) {
+          await sdk.session.command({
+            sessionID,
+            agent,
+            model: args.model,
+            command: args.command,
+            arguments: message,
+            variant: args.variant,
+          })
+        } else {
+          const model = args.model ? Provider.parseModel(args.model) : undefined
+          await sdk.session.prompt({
+            sessionID,
+            agent,
+            model,
+            variant: args.variant,
+            parts: [...files, { type: "text", text: message }],
+          })
+        }
+      }
 
-      if (args.command) {
-        await sdk.session.command({
-          sessionID,
-          agent,
-          model: args.model,
-          command: args.command,
-          arguments: message,
-          variant: args.variant,
-        })
-      } else {
-        const model = args.model ? Provider.parseModel(args.model) : undefined
-        await sdk.session.prompt({
-          sessionID,
-          agent,
-          model,
-          variant: args.variant,
-          parts: [...files, { type: "text", text: message }],
-        })
+      if (args.attach) {
+        const headers = (() => {
+          const password = args.password ?? process.env.OPENCODE_SERVER_PASSWORD
+          if (!password) return undefined
+          const username = process.env.OPENCODE_SERVER_USERNAME ?? "opencode"
+          const auth = `Basic ${Buffer.from(`${username}:${password}`).toString("base64")}`
+          return { Authorization: auth }
+        })()
+        const sdk = createOpencodeClient({ baseUrl: args.attach, directory, headers })
+        return await execute(sdk)
       }
-    }
-
-    if (args.attach) {
-      const headers = (() => {
-        const password = args.password ?? process.env.OPENCODE_SERVER_PASSWORD
-        if (!password) return undefined
-        const username = process.env.OPENCODE_SERVER_USERNAME ?? "opencode"
-        const auth = `Basic ${Buffer.from(`${username}:${password}`).toString("base64")}`
-        return { Authorization: auth }
-      })()
-      const sdk = createOpencodeClient({ baseUrl: args.attach, directory, headers })
-      return await execute(sdk)
-    }
-
-    const fetchFn = (async (input: RequestInfo | URL, init?: RequestInit) => {
-      const request = new Request(input, init)
-      return Server.Default().app.fetch(request)
-    }) as typeof globalThis.fetch
-    const sdk = createOpencodeClient({ baseUrl: "http://opencode.internal", fetch: fetchFn })
-    await execute(sdk)
+
+      const fetchFn = (async (input: RequestInfo | URL, init?: RequestInit) => {
+        const request = new Request(input, init)
+        return Server.Default().app.fetch(request)
+      }) as typeof globalThis.fetch
+      const sdk = createOpencodeClient({ baseUrl: "http://opencode.internal", fetch: fetchFn })
+      await execute(sdk)
     })
   }),
 })