mirror of
https://github.com/harivansh-afk/sandbox-agent.git
synced 2026-04-15 07:04:48 +00:00
fix: opencode compat tool call rendering and default to no-token (#95)
- Fix tool name lost on ToolResult events (persist via tool_name_by_call) - Fix tool input lost on ToolResult events (persist via tool_args_by_call) - Fix tool output in wrong field (error -> output) - Fix text doubling in streaming (defer emit to ItemCompleted) - Fix missing delta field in text streaming events - Default server mode to no-token when --token not specified - Add install-fast-sa and install-fast-gigacode justfile targets
This commit is contained in:
parent
a02393436c
commit
6a3345b954
32 changed files with 9193 additions and 38 deletions
|
|
@ -419,8 +419,6 @@ pub struct CredentialsExtractEnvArgs {
|
|||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum CliError {
|
||||
#[error("missing --token or --no-token for server mode")]
|
||||
MissingToken,
|
||||
#[error("invalid cors origin: {0}")]
|
||||
InvalidCorsOrigin(String),
|
||||
#[error("invalid cors method: {0}")]
|
||||
|
|
@ -489,12 +487,10 @@ pub fn run_command(command: &Command, cli: &CliConfig) -> Result<(), CliError> {
|
|||
}
|
||||
|
||||
fn run_server(cli: &CliConfig, server: &ServerArgs) -> Result<(), CliError> {
|
||||
let auth = if cli.no_token {
|
||||
AuthConfig::disabled()
|
||||
} else if let Some(token) = cli.token.clone() {
|
||||
let auth = if let Some(token) = cli.token.clone() {
|
||||
AuthConfig::with_token(token)
|
||||
} else {
|
||||
return Err(CliError::MissingToken);
|
||||
AuthConfig::disabled()
|
||||
};
|
||||
|
||||
let agent_manager = AgentManager::new(default_install_dir())
|
||||
|
|
|
|||
|
|
@ -222,12 +222,8 @@ pub fn spawn_sandbox_agent_daemon(
|
|||
.stdout(Stdio::from(log_file))
|
||||
.stderr(Stdio::from(log_file_err));
|
||||
|
||||
if cli.no_token {
|
||||
cmd.arg("--no-token");
|
||||
} else if let Some(token) = token {
|
||||
if let Some(token) = token {
|
||||
cmd.arg("--token").arg(token);
|
||||
} else {
|
||||
return Err(CliError::MissingToken);
|
||||
}
|
||||
|
||||
cmd.spawn().map_err(CliError::from)
|
||||
|
|
|
|||
|
|
@ -212,6 +212,10 @@ struct OpenCodeSessionRuntime {
|
|||
part_id_by_message: HashMap<String, String>,
|
||||
tool_part_by_call: HashMap<String, String>,
|
||||
tool_message_by_call: HashMap<String, String>,
|
||||
/// Tool name by call_id, persisted from ToolCall for use in ToolResult events
|
||||
tool_name_by_call: HashMap<String, String>,
|
||||
/// Tool arguments by call_id, persisted from ToolCall for use in ToolResult events
|
||||
tool_args_by_call: HashMap<String, String>,
|
||||
}
|
||||
|
||||
pub struct OpenCodeState {
|
||||
|
|
@ -1586,25 +1590,48 @@ async fn apply_item_event(
|
|||
.entry(message_id.clone())
|
||||
.or_insert_with(|| format!("{}_text", message_id))
|
||||
.clone();
|
||||
runtime
|
||||
.text_by_message
|
||||
.insert(message_id.clone(), text.clone());
|
||||
let part = build_text_part_with_id(&session_id, &message_id, &part_id, &text);
|
||||
upsert_message_part(&state.opencode, &session_id, &message_id, part.clone()).await;
|
||||
state
|
||||
.opencode
|
||||
.emit_event(part_event("message.part.updated", &part));
|
||||
let _ = state
|
||||
.opencode
|
||||
.update_runtime(&session_id, |runtime| {
|
||||
runtime
|
||||
.text_by_message
|
||||
.insert(message_id.clone(), text.clone());
|
||||
runtime
|
||||
.part_id_by_message
|
||||
.insert(message_id.clone(), part_id.clone());
|
||||
})
|
||||
.await;
|
||||
if event.event_type == UniversalEventType::ItemStarted {
|
||||
// For ItemStarted, only store the text in runtime as the initial value
|
||||
// without emitting a part event. Deltas will handle streaming, and
|
||||
// ItemCompleted will emit the final text part.
|
||||
let _ = state
|
||||
.opencode
|
||||
.update_runtime(&session_id, |runtime| {
|
||||
runtime
|
||||
.text_by_message
|
||||
.insert(message_id.clone(), String::new());
|
||||
runtime
|
||||
.part_id_by_message
|
||||
.insert(message_id.clone(), part_id.clone());
|
||||
})
|
||||
.await;
|
||||
} else {
|
||||
// For ItemCompleted, emit the final text part with the complete text.
|
||||
// Use the accumulated text from deltas if available, otherwise use
|
||||
// the text from the completed event.
|
||||
let final_text = runtime
|
||||
.text_by_message
|
||||
.get(&message_id)
|
||||
.filter(|t| !t.is_empty())
|
||||
.cloned()
|
||||
.unwrap_or_else(|| text.clone());
|
||||
let part = build_text_part_with_id(&session_id, &message_id, &part_id, &final_text);
|
||||
upsert_message_part(&state.opencode, &session_id, &message_id, part.clone()).await;
|
||||
state
|
||||
.opencode
|
||||
.emit_event(part_event("message.part.updated", &part));
|
||||
let _ = state
|
||||
.opencode
|
||||
.update_runtime(&session_id, |runtime| {
|
||||
runtime
|
||||
.text_by_message
|
||||
.insert(message_id.clone(), final_text.clone());
|
||||
runtime
|
||||
.part_id_by_message
|
||||
.insert(message_id.clone(), part_id.clone());
|
||||
})
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
||||
for part in item.content.iter() {
|
||||
|
|
@ -1634,9 +1661,10 @@ async fn apply_item_event(
|
|||
.entry(call_id.clone())
|
||||
.or_insert_with(|| next_id("part_", &PART_COUNTER))
|
||||
.clone();
|
||||
let input_value = tool_input_from_arguments(Some(arguments.as_str()));
|
||||
let state_value = json!({
|
||||
"status": "pending",
|
||||
"input": {"arguments": arguments},
|
||||
"input": input_value,
|
||||
"raw": arguments,
|
||||
});
|
||||
let tool_part = build_tool_part(
|
||||
|
|
@ -1661,6 +1689,12 @@ async fn apply_item_event(
|
|||
runtime
|
||||
.tool_message_by_call
|
||||
.insert(call_id.clone(), message_id.clone());
|
||||
runtime
|
||||
.tool_name_by_call
|
||||
.insert(call_id.clone(), name.clone());
|
||||
runtime
|
||||
.tool_args_by_call
|
||||
.insert(call_id.clone(), arguments.clone());
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
|
@ -1670,9 +1704,22 @@ async fn apply_item_event(
|
|||
.entry(call_id.clone())
|
||||
.or_insert_with(|| next_id("part_", &PART_COUNTER))
|
||||
.clone();
|
||||
// Resolve tool name from stored ToolCall data
|
||||
let tool_name = runtime
|
||||
.tool_name_by_call
|
||||
.get(call_id)
|
||||
.cloned()
|
||||
.unwrap_or_else(|| "tool".to_string());
|
||||
// Resolve input from stored ToolCall arguments
|
||||
let input_value = runtime
|
||||
.tool_args_by_call
|
||||
.get(call_id)
|
||||
.and_then(|args| tool_input_from_arguments(Some(args.as_str())).as_object().cloned())
|
||||
.map(Value::Object)
|
||||
.unwrap_or_else(|| json!({}));
|
||||
let state_value = json!({
|
||||
"status": "completed",
|
||||
"input": {},
|
||||
"input": input_value,
|
||||
"output": output,
|
||||
"title": "Tool result",
|
||||
"metadata": {},
|
||||
|
|
@ -1684,7 +1731,7 @@ async fn apply_item_event(
|
|||
&message_id,
|
||||
&part_id,
|
||||
call_id,
|
||||
"tool",
|
||||
&tool_name,
|
||||
state_value,
|
||||
);
|
||||
upsert_message_part(&state.opencode, &session_id, &message_id, tool_part.clone())
|
||||
|
|
@ -1877,12 +1924,19 @@ async fn apply_tool_item_event(
|
|||
.get(&call_id)
|
||||
.cloned()
|
||||
.unwrap_or_else(|| next_id("part_", &PART_COUNTER));
|
||||
// Resolve tool name: prefer current event's data, fall back to stored value from ToolCall
|
||||
let tool_name = tool_info
|
||||
.tool_name
|
||||
.clone()
|
||||
.or_else(|| runtime.tool_name_by_call.get(&call_id).cloned())
|
||||
.unwrap_or_else(|| "tool".to_string());
|
||||
let input_value = tool_input_from_arguments(tool_info.arguments.as_deref());
|
||||
let raw_args = tool_info.arguments.clone().unwrap_or_default();
|
||||
// Resolve arguments: prefer current event's data, fall back to stored value from ToolCall
|
||||
let effective_arguments = tool_info
|
||||
.arguments
|
||||
.clone()
|
||||
.or_else(|| runtime.tool_args_by_call.get(&call_id).cloned());
|
||||
let input_value = tool_input_from_arguments(effective_arguments.as_deref());
|
||||
let raw_args = effective_arguments.clone().unwrap_or_default();
|
||||
let output_value = tool_info
|
||||
.output
|
||||
.clone()
|
||||
|
|
@ -1910,7 +1964,7 @@ async fn apply_tool_item_event(
|
|||
json!({
|
||||
"status": "error",
|
||||
"input": input_value,
|
||||
"error": output_value.unwrap_or_else(|| "Tool failed".to_string()),
|
||||
"output": output_value.unwrap_or_else(|| "Tool failed".to_string()),
|
||||
"metadata": {},
|
||||
"time": {"start": now, "end": now},
|
||||
})
|
||||
|
|
@ -1962,6 +2016,17 @@ async fn apply_tool_item_event(
|
|||
runtime
|
||||
.tool_message_by_call
|
||||
.insert(call_id.clone(), message_id.clone());
|
||||
// Persist tool name and arguments from ToolCall for later ToolResult events
|
||||
if let Some(name) = tool_info.tool_name.as_ref() {
|
||||
runtime
|
||||
.tool_name_by_call
|
||||
.insert(call_id.clone(), name.clone());
|
||||
}
|
||||
if let Some(args) = tool_info.arguments.as_ref() {
|
||||
runtime
|
||||
.tool_args_by_call
|
||||
.insert(call_id.clone(), args.clone());
|
||||
}
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
|
@ -2071,7 +2136,7 @@ async fn apply_item_delta(
|
|||
upsert_message_part(&state.opencode, &session_id, &message_id, part.clone()).await;
|
||||
state
|
||||
.opencode
|
||||
.emit_event(part_event("message.part.updated", &part));
|
||||
.emit_event(part_event_with_delta("message.part.updated", &part, Some(&delta)));
|
||||
let _ = state
|
||||
.opencode
|
||||
.update_runtime(&session_id, |runtime| {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue