agent_commander/tools/
opencode.rs1use crate::streaming::parse_ndjson;
5use crate::tools::shell::{build_command_head, escape_arg, escape_single_quotes};
6use serde_json::Value;
7use std::collections::HashMap;
8
9pub const READ_ONLY_PERMISSION: &str = r#"{"edit":"deny","bash":"deny","task":"deny"}"#;
11
12pub fn get_model_map() -> HashMap<&'static str, &'static str> {
14 let mut map = HashMap::new();
15 map.insert("gpt4", "openai/gpt-4");
16 map.insert("gpt4o", "openai/gpt-4o");
17 map.insert("claude", "anthropic/claude-3-5-sonnet");
18 map.insert("sonnet", "anthropic/claude-3-5-sonnet");
19 map.insert("opus", "anthropic/claude-3-opus");
20 map.insert("gemini", "google/gemini-pro");
21 map.insert("grok", "opencode/grok-code");
22 map.insert("grok-code", "opencode/grok-code");
23 map.insert("grok-code-fast-1", "opencode/grok-code");
24 map
25}
26
27pub fn map_model_to_id(model: &str) -> String {
35 let model_map = get_model_map();
36 model_map
37 .get(model)
38 .map(|s| s.to_string())
39 .unwrap_or_else(|| model.to_string())
40}
41
42#[derive(Debug, Clone, Default)]
44pub struct OpencodeBuildOptions {
45 pub prompt: Option<String>,
46 pub prompt_file: Option<String>,
47 pub system_prompt: Option<String>,
48 pub model: Option<String>,
49 pub json: bool,
50 pub resume: Option<String>,
51 pub read_only: bool,
52 pub executable: Option<String>,
53 pub extra_env: Vec<(String, String)>,
54 pub extra_args: Vec<String>,
55}
56
57pub fn build_args(options: &OpencodeBuildOptions) -> Vec<String> {
65 let mut args = vec!["run".to_string()];
66
67 if let Some(ref model) = options.model {
68 let mapped_model = map_model_to_id(model);
69 args.push("--model".to_string());
70 args.push(mapped_model);
71 }
72
73 if options.json {
75 args.push("--format".to_string());
76 args.push("json".to_string());
77 }
78
79 if let Some(ref resume) = options.resume {
80 args.push("--resume".to_string());
81 args.push(resume.clone());
82 }
83
84 args.extend(options.extra_args.clone());
85
86 args
87}
88
89pub fn build_command(options: &OpencodeBuildOptions) -> String {
98 let args = build_args(options);
99 let args_str: Vec<String> = args.iter().map(|a| escape_arg(a)).collect();
100
101 let combined_prompt = match (&options.system_prompt, &options.prompt) {
103 (Some(sys), Some(prompt)) => format!("{}\n\n{}", sys, prompt),
104 (Some(sys), None) => sys.clone(),
105 (None, Some(prompt)) => prompt.clone(),
106 (None, None) => String::new(),
107 };
108
109 let input_command = options.prompt_file.as_ref().map_or_else(
111 || format!("printf '%s' '{}'", escape_single_quotes(&combined_prompt)),
112 |prompt_file| format!("cat {}", escape_arg(prompt_file)),
113 );
114 let executable = options.executable.as_deref().unwrap_or("opencode");
115 let mut extra_env = Vec::new();
116 if options.read_only {
117 extra_env.push((
118 "OPENCODE_PERMISSION".to_string(),
119 READ_ONLY_PERMISSION.to_string(),
120 ));
121 }
122 extra_env.extend(options.extra_env.clone());
123
124 format!(
125 "{} | {} {}",
126 input_command,
127 build_command_head(executable, &extra_env, &[]),
128 args_str.join(" ")
129 )
130 .trim()
131 .to_string()
132}
133
134pub fn parse_output(output: &str) -> Vec<Value> {
143 parse_ndjson(output)
144}
145
146pub fn extract_session_id(output: &str) -> Option<String> {
154 let messages = parse_output(output);
155
156 for msg in messages {
157 if let Some(session_id) = msg.get("session_id").and_then(|v| v.as_str()) {
158 return Some(session_id.to_string());
159 }
160 }
161
162 None
163}
164
165#[derive(Debug, Clone, Default)]
167pub struct OpencodeUsage {
168 pub input_tokens: u64,
169 pub output_tokens: u64,
170}
171
172pub fn extract_usage(output: &str) -> OpencodeUsage {
180 let messages = parse_output(output);
181 let mut usage = OpencodeUsage::default();
182
183 for msg in messages {
184 if let Some(msg_usage) = msg.get("usage") {
185 if let Some(input) = msg_usage.get("input_tokens").and_then(|v| v.as_u64()) {
186 usage.input_tokens += input;
187 }
188 if let Some(output) = msg_usage.get("output_tokens").and_then(|v| v.as_u64()) {
189 usage.output_tokens += output;
190 }
191 }
192 }
193
194 usage
195}
196
197#[derive(Debug, Clone)]
199pub struct OpencodeTool {
200 pub name: &'static str,
201 pub display_name: &'static str,
202 pub executable: &'static str,
203 pub supports_json_output: bool,
204 pub supports_json_input: bool,
205 pub supports_system_prompt: bool,
206 pub supports_resume: bool,
207 pub supports_read_only: bool,
208 pub supports_ask: bool,
209 pub default_model: &'static str,
210}
211
212impl Default for OpencodeTool {
213 fn default() -> Self {
214 Self {
215 name: "opencode",
216 display_name: "OpenCode CLI",
217 executable: "opencode",
218 supports_json_output: true,
219 supports_json_input: true, supports_system_prompt: false, supports_resume: true,
222 supports_read_only: true, supports_ask: false, default_model: "grok-code-fast-1",
225 }
226 }
227}
228
229