// OpenCode CLI argument and JSONL contracts:
// https://dev.opencode.ai/docs/cli/
// https://github.com/anomalyco/opencode/blob/1e17856ba4b5b052650c8115060852f3f023844e/packages/opencode/src/cli/cmd/run.ts#L416-L428
///|
priv struct OpenCodeExecArgs {
input : String
session_id : String?
files : Array[@path.Path]
model : String?
agent : String?
working_directory : @path.Path?
variant : String?
title : String?
thinking : Bool
}
///|
/// Process adapter for `opencode run --format json`.
struct OpenCodeExec {
executable_path : @path.Path
env_override : Map[String, String]?
config : OpenCodeConfigObject?
}
///|
/// Construct the process adapter used by an OpenCode client.
fn OpenCodeExec::OpenCodeExec(
executable_path : @path.Path?,
env : Map[String, String]?,
config : OpenCodeConfigObject?,
) -> OpenCodeExec {
{
executable_path: executable_path.unwrap_or(@path.Path("opencode")),
env_override: env,
config,
}
}
///|
/// Execute the OpenCode CLI and deliver each decoded event to `on_event`.
async fn OpenCodeExec::run(
self : OpenCodeExec,
args : OpenCodeExecArgs,
on_event : async (ThreadEvent) -> Bool,
) -> Unit {
let command_args = ["run", "--format", "json"]
if args.session_id is Some(session_id) {
command_args.push("--session")
command_args.push(session_id)
}
if args.model is Some(model) {
command_args.push("--model")
command_args.push(model)
}
if args.agent is Some(agent) {
command_args.push("--agent")
command_args.push(agent)
}
if args.working_directory is Some(directory) {
command_args.push("--dir")
command_args.push(directory.to_string())
}
if args.variant is Some(variant) {
command_args.push("--variant")
command_args.push(variant)
}
if args.title is Some(title) {
command_args.push("--title")
command_args.push(title)
}
if args.thinking {
command_args.push("--thinking")
}
for file in args.files {
command_args.push("--file")
command_args.push(file.to_string())
}
let environment = match self.env_override {
Some(environment_override) => @copy.Copy::copy(environment_override)
None => Map([])
}
if self.config is Some(config) {
environment["OPENCODE_CONFIG_CONTENT"] = config.to_json().stringify()
}
let inherit_environment = self.env_override is None
let result = @agent_cli.run(
@agent_cli.Invocation::new(
command=self.executable_path.to_string(),
arguments=command_args,
environment~,
inherit_environment~,
input=args.input,
),
on_event,
) catch {
@agent_cli.AgentCliError::InvalidJson(line~, cause~) =>
raise InvalidEvent(
message="Failed to parse OpenCode event: \{line}; cause: \{cause}",
)
error => raise error
}
match result {
@agent_cli.RunResult::Completed | @agent_cli.RunResult::Stopped => ()
@agent_cli.RunResult::Failed(code~, stderr~) =>
raise ExecFailed(code~, stderr~)
}
}