chore: generate

This commit is contained in:
opencode-agent[bot]
2026-05-03 02:36:41 +00:00
parent 7409dcc6bd
commit 61150f6391
+327 -327
View File
@@ -300,288 +300,310 @@ export const RunCommand = effectCmd({
}), }),
handler: Effect.fn("Cli.run")(function* (args) { handler: Effect.fn("Cli.run")(function* (args) {
yield* Effect.promise(async () => { yield* Effect.promise(async () => {
let message = [...args.message, ...(args["--"] || [])] let message = [...args.message, ...(args["--"] || [])]
.map((arg) => (arg.includes(" ") ? `"${arg.replace(/"/g, '\\"')}"` : arg)) .map((arg) => (arg.includes(" ") ? `"${arg.replace(/"/g, '\\"')}"` : arg))
.join(" ") .join(" ")
const directory = (() => { const directory = (() => {
if (!args.dir) return undefined if (!args.dir) return undefined
if (args.attach) return args.dir if (args.attach) return args.dir
try { try {
process.chdir(args.dir) process.chdir(args.dir)
return process.cwd() return process.cwd()
} catch { } catch {
UI.error("Failed to change directory to " + args.dir) 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]
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) 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({ for (const filePath of list) {
type: "file", const resolvedPath = path.resolve(process.cwd(), filePath)
url: pathToFileURL(resolvedPath).href, if (!(await Filesystem.exists(resolvedPath))) {
filename: path.basename(resolvedPath), UI.error(`File not found: ${filePath}`)
mime, process.exit(1)
})
}
}
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 (baseID) return baseID
const name = title()
const result = await sdk.session.create({ title: name, permission: rules })
return result.data?.id
}
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)
}
}
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)
}
}
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
}
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)
} }
if (event.type === "message.part.updated") { const mime = (await Filesystem.isDir(resolvedPath)) ? "application/x-directory" : "text/plain"
const part = event.properties.part
if (part.sessionID !== sessionID) continue
if (part.type === "tool" && (part.state.status === "completed" || part.state.status === "error")) { files.push({
if (emit("tool_use", { part })) continue type: "file",
if (part.state.status === "completed") { url: pathToFileURL(resolvedPath).href,
tool(part) filename: path.basename(resolvedPath),
continue 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 (baseID) return baseID
const name = title()
const result = await sdk.session.create({ title: name, permission: rules })
return result.data?.id
}
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)
}
}
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)
}
}
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
}
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)
}
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)
} }
inline({
icon: "✗", if (
title: `${part.tool} failed`, part.type === "tool" &&
}) part.tool === "task" &&
UI.error(part.state.error) 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 === "step-start") {
if (emit("step_start", { part })) continue
}
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(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) {
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)
}
error = error ? error + EOL + err : err
if (emit("error", { error: props.error })) continue
UI.error(err)
} }
if ( if (
part.type === "tool" && event.type === "session.status" &&
part.tool === "task" && event.properties.sessionID === sessionID &&
part.state.status === "running" && event.properties.status.type === "idle"
args.format !== "json"
) { ) {
if (toggles.get(part.id) === true) continue break
task(props<typeof TaskTool>(part))
toggles.set(part.id, true)
} }
if (part.type === "step-start") { if (event.type === "permission.asked") {
if (emit("step_start", { part })) continue const permission = event.properties
} if (permission.sessionID !== sessionID) continue
if (part.type === "step-finish") { if (args["dangerously-skip-permissions"]) {
if (emit("step_finish", { part })) continue await sdk.permission.reply({
} requestID: permission.id,
reply: "once",
if (part.type === "text" && part.time?.end) { })
if (emit("text", { part })) continue } else {
const text = part.text.trim() UI.println(
if (!text) continue UI.Style.TEXT_WARNING_BOLD + "!",
if (!process.stdout.isTTY) { UI.Style.TEXT_NORMAL +
process.stdout.write(text + EOL) `permission requested: ${permission.permission} (${permission.patterns.join(", ")}); auto-rejecting`,
continue )
await sdk.permission.reply({
requestID: permission.id,
reply: "reject",
})
} }
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) {
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)
}
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",
})
} }
} }
} }
}
// Validate agent if specified // Validate agent if specified
const agent = await (async () => { const agent = await (async () => {
if (!args.agent) return undefined if (!args.agent) return undefined
const name = args.agent const name = args.agent
// When attaching, validate against the running server instead of local Instance state. // When attaching, validate against the running server instead of local Instance state.
if (args.attach) { if (args.attach) {
const modes = await sdk.app const modes = await sdk.app
.agents(undefined, { throwOnError: true }) .agents(undefined, { throwOnError: true })
.then((x) => x.data ?? []) .then((x) => x.data ?? [])
.catch(() => undefined) .catch(() => undefined)
if (!modes) { if (!modes) {
UI.println( UI.println(
UI.Style.TEXT_WARNING_BOLD + "!", UI.Style.TEXT_WARNING_BOLD + "!",
UI.Style.TEXT_NORMAL, UI.Style.TEXT_NORMAL,
`failed to list agents from ${args.attach}. Falling back to default agent`, `failed to list agents from ${args.attach}. Falling back to default agent`,
) )
return undefined return undefined
}
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
}
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
}
return name
} }
const agent = modes.find((a) => a.name === name) const entry = await AppRuntime.runPromise(Agent.Service.use((svc) => svc.get(name)))
if (!agent) { if (!entry) {
UI.println( UI.println(
UI.Style.TEXT_WARNING_BOLD + "!", UI.Style.TEXT_WARNING_BOLD + "!",
UI.Style.TEXT_NORMAL, UI.Style.TEXT_NORMAL,
@@ -589,8 +611,7 @@ export const RunCommand = effectCmd({
) )
return undefined return undefined
} }
if (entry.mode === "subagent") {
if (agent.mode === "subagent") {
UI.println( UI.println(
UI.Style.TEXT_WARNING_BOLD + "!", UI.Style.TEXT_WARNING_BOLD + "!",
UI.Style.TEXT_NORMAL, UI.Style.TEXT_NORMAL,
@@ -598,81 +619,60 @@ export const RunCommand = effectCmd({
) )
return undefined return undefined
} }
return name return name
})()
const sessionID = await session(sdk)
if (!sessionID) {
UI.error("Session not found")
process.exit(1)
} }
await share(sdk, sessionID)
const entry = await AppRuntime.runPromise(Agent.Service.use((svc) => svc.get(name))) loop().catch((e) => {
if (!entry) { console.error(e)
UI.println( process.exit(1)
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
}
return name
})()
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)
})
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) { if (args.attach) {
const headers = (() => { const headers = (() => {
const password = args.password ?? process.env.OPENCODE_SERVER_PASSWORD const password = args.password ?? process.env.OPENCODE_SERVER_PASSWORD
if (!password) return undefined if (!password) return undefined
const username = process.env.OPENCODE_SERVER_USERNAME ?? "opencode" const username = process.env.OPENCODE_SERVER_USERNAME ?? "opencode"
const auth = `Basic ${Buffer.from(`${username}:${password}`).toString("base64")}` const auth = `Basic ${Buffer.from(`${username}:${password}`).toString("base64")}`
return { Authorization: auth } return { Authorization: auth }
})() })()
const sdk = createOpencodeClient({ baseUrl: args.attach, directory, headers }) const sdk = createOpencodeClient({ baseUrl: args.attach, directory, headers })
return await execute(sdk) return await execute(sdk)
} }
const fetchFn = (async (input: RequestInfo | URL, init?: RequestInit) => { const fetchFn = (async (input: RequestInfo | URL, init?: RequestInit) => {
const request = new Request(input, init) const request = new Request(input, init)
return Server.Default().app.fetch(request) return Server.Default().app.fetch(request)
}) as typeof globalThis.fetch }) as typeof globalThis.fetch
const sdk = createOpencodeClient({ baseUrl: "http://opencode.internal", fetch: fetchFn }) const sdk = createOpencodeClient({ baseUrl: "http://opencode.internal", fetch: fetchFn })
await execute(sdk) await execute(sdk)
}) })
}), }),
}) })