mirror of
https://github.com/harivansh-afk/sandbox-agent.git
synced 2026-04-15 07:04:48 +00:00
fix: OpenCode event streaming + bypass permission mode
Three independent fixes for the OpenCode agent adapter:
1. Wrong API endpoints: /event/subscribe → /event, /session/{id}/prompt → /session/{id}/message
2. Untagged enum mis-dispatch: replace serde_json::from_value with manual type-field dispatch
3. Wire permissionMode "bypass" for OpenCode: allow in normalize_permission_mode() and pass
--dangerously-skip-permissions to CLI (both spawn and spawn_streaming)
Tested with OpenCode 1.1.48 + Kimi K2.5.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
87a4e81d31
commit
9c7a08a165
2 changed files with 99 additions and 14 deletions
|
|
@ -268,6 +268,9 @@ impl AgentManager {
|
||||||
if let Some(variant) = options.variant.as_deref() {
|
if let Some(variant) = options.variant.as_deref() {
|
||||||
command.arg("--variant").arg(variant);
|
command.arg("--variant").arg(variant);
|
||||||
}
|
}
|
||||||
|
if options.permission_mode.as_deref() == Some("bypass") {
|
||||||
|
command.arg("--dangerously-skip-permissions");
|
||||||
|
}
|
||||||
if let Some(session_id) = options.session_id.as_deref() {
|
if let Some(session_id) = options.session_id.as_deref() {
|
||||||
command.arg("-s").arg(session_id);
|
command.arg("-s").arg(session_id);
|
||||||
}
|
}
|
||||||
|
|
@ -632,6 +635,9 @@ impl AgentManager {
|
||||||
if let Some(variant) = options.variant.as_deref() {
|
if let Some(variant) = options.variant.as_deref() {
|
||||||
command.arg("--variant").arg(variant);
|
command.arg("--variant").arg(variant);
|
||||||
}
|
}
|
||||||
|
if options.permission_mode.as_deref() == Some("bypass") {
|
||||||
|
command.arg("--dangerously-skip-permissions");
|
||||||
|
}
|
||||||
if let Some(session_id) = options.session_id.as_deref() {
|
if let Some(session_id) = options.session_id.as_deref() {
|
||||||
command.arg("-s").arg(session_id);
|
command.arg("-s").arg(session_id);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,12 +24,12 @@ use reqwest::Client;
|
||||||
use sandbox_agent_error::{AgentError, ErrorType, ProblemDetails, SandboxError};
|
use sandbox_agent_error::{AgentError, ErrorType, ProblemDetails, SandboxError};
|
||||||
use sandbox_agent_universal_agent_schema::{
|
use sandbox_agent_universal_agent_schema::{
|
||||||
codex as codex_schema, convert_amp, convert_claude, convert_codex, convert_opencode,
|
codex as codex_schema, convert_amp, convert_claude, convert_codex, convert_opencode,
|
||||||
turn_ended_event, turn_started_event, AgentUnparsedData, ContentPart, ErrorData,
|
opencode as opencode_schema, turn_ended_event, turn_started_event, AgentUnparsedData,
|
||||||
EventConversion, EventSource, FileAction, ItemDeltaData, ItemEventData, ItemKind, ItemRole,
|
ContentPart, ErrorData, EventConversion, EventSource, FileAction, ItemDeltaData, ItemEventData,
|
||||||
ItemStatus, PermissionEventData, PermissionStatus, QuestionEventData, QuestionStatus,
|
ItemKind, ItemRole, ItemStatus, PermissionEventData, PermissionStatus, QuestionEventData,
|
||||||
ReasoningVisibility, SessionEndReason, SessionEndedData, SessionStartedData, StderrOutput,
|
QuestionStatus, ReasoningVisibility, SessionEndReason, SessionEndedData, SessionStartedData,
|
||||||
TerminatedBy, TurnEventData, TurnPhase, UniversalEvent, UniversalEventData, UniversalEventType,
|
StderrOutput, TerminatedBy, TurnEventData, TurnPhase, UniversalEvent, UniversalEventData,
|
||||||
UniversalItem,
|
UniversalEventType, UniversalItem,
|
||||||
};
|
};
|
||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
@ -3655,7 +3655,7 @@ impl SessionManager {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let url = format!("{base_url}/event/subscribe");
|
let url = format!("{base_url}/event");
|
||||||
let response = match self.http_client.get(url).send().await {
|
let response = match self.http_client.get(url).send().await {
|
||||||
Ok(response) => response,
|
Ok(response) => response,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
|
|
@ -3746,12 +3746,91 @@ impl SessionManager {
|
||||||
if !opencode_event_matches_session(&value, &native_session_id) {
|
if !opencode_event_matches_session(&value, &native_session_id) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let conversions = match serde_json::from_value(value.clone()) {
|
// Manual type-based dispatch to bypass broken #[serde(untagged)]
|
||||||
Ok(event) => match convert_opencode::event_to_universal(&event) {
|
// enum ordering where ServerConnected (variant #5, empty properties)
|
||||||
Ok(conversions) => conversions,
|
// matches all events before MessageUpdated (variant #10) gets tried.
|
||||||
Err(err) => vec![agent_unparsed("opencode", &err, value.clone())],
|
let event_type = value.get("type").and_then(|t| t.as_str()).unwrap_or("");
|
||||||
},
|
let conversions = match event_type {
|
||||||
Err(err) => vec![agent_unparsed("opencode", &err.to_string(), value.clone())],
|
"message.updated" => {
|
||||||
|
match serde_json::from_value::<opencode_schema::EventMessageUpdated>(value.clone()) {
|
||||||
|
Ok(e) => match convert_opencode::event_to_universal(&opencode_schema::Event::MessageUpdated(e)) {
|
||||||
|
Ok(c) => c,
|
||||||
|
Err(err) => vec![agent_unparsed("opencode", &err, value.clone())],
|
||||||
|
},
|
||||||
|
Err(err) => vec![agent_unparsed("opencode", &format!("message.updated: {}", err), value.clone())],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"message.part.updated" => {
|
||||||
|
match serde_json::from_value::<opencode_schema::EventMessagePartUpdated>(value.clone()) {
|
||||||
|
Ok(e) => match convert_opencode::event_to_universal(&opencode_schema::Event::MessagePartUpdated(e)) {
|
||||||
|
Ok(c) => c,
|
||||||
|
Err(err) => vec![agent_unparsed("opencode", &err, value.clone())],
|
||||||
|
},
|
||||||
|
Err(err) => vec![agent_unparsed("opencode", &format!("message.part.updated: {}", err), value.clone())],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"question.asked" => {
|
||||||
|
match serde_json::from_value::<opencode_schema::EventQuestionAsked>(value.clone()) {
|
||||||
|
Ok(e) => match convert_opencode::event_to_universal(&opencode_schema::Event::QuestionAsked(e)) {
|
||||||
|
Ok(c) => c,
|
||||||
|
Err(err) => vec![agent_unparsed("opencode", &err, value.clone())],
|
||||||
|
},
|
||||||
|
Err(err) => vec![agent_unparsed("opencode", &format!("question.asked: {}", err), value.clone())],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"permission.asked" => {
|
||||||
|
match serde_json::from_value::<opencode_schema::EventPermissionAsked>(value.clone()) {
|
||||||
|
Ok(e) => match convert_opencode::event_to_universal(&opencode_schema::Event::PermissionAsked(e)) {
|
||||||
|
Ok(c) => c,
|
||||||
|
Err(err) => vec![agent_unparsed("opencode", &err, value.clone())],
|
||||||
|
},
|
||||||
|
Err(err) => vec![agent_unparsed("opencode", &format!("permission.asked: {}", err), value.clone())],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"session.created" => {
|
||||||
|
match serde_json::from_value::<opencode_schema::EventSessionCreated>(value.clone()) {
|
||||||
|
Ok(e) => match convert_opencode::event_to_universal(&opencode_schema::Event::SessionCreated(e)) {
|
||||||
|
Ok(c) => c,
|
||||||
|
Err(err) => vec![agent_unparsed("opencode", &err, value.clone())],
|
||||||
|
},
|
||||||
|
Err(err) => vec![agent_unparsed("opencode", &format!("session.created: {}", err), value.clone())],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"session.status" => {
|
||||||
|
match serde_json::from_value::<opencode_schema::EventSessionStatus>(value.clone()) {
|
||||||
|
Ok(e) => match convert_opencode::event_to_universal(&opencode_schema::Event::SessionStatus(e)) {
|
||||||
|
Ok(c) => c,
|
||||||
|
Err(err) => vec![agent_unparsed("opencode", &err, value.clone())],
|
||||||
|
},
|
||||||
|
Err(err) => vec![agent_unparsed("opencode", &format!("session.status: {}", err), value.clone())],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"session.idle" => {
|
||||||
|
match serde_json::from_value::<opencode_schema::EventSessionIdle>(value.clone()) {
|
||||||
|
Ok(e) => match convert_opencode::event_to_universal(&opencode_schema::Event::SessionIdle(e)) {
|
||||||
|
Ok(c) => c,
|
||||||
|
Err(err) => vec![agent_unparsed("opencode", &err, value.clone())],
|
||||||
|
},
|
||||||
|
Err(err) => vec![agent_unparsed("opencode", &format!("session.idle: {}", err), value.clone())],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"session.error" => {
|
||||||
|
match serde_json::from_value::<opencode_schema::EventSessionError>(value.clone()) {
|
||||||
|
Ok(e) => match convert_opencode::event_to_universal(&opencode_schema::Event::SessionError(e)) {
|
||||||
|
Ok(c) => c,
|
||||||
|
Err(err) => vec![agent_unparsed("opencode", &err, value.clone())],
|
||||||
|
},
|
||||||
|
Err(err) => vec![agent_unparsed("opencode", &format!("session.error: {}", err), value.clone())],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Informational events we can safely skip
|
||||||
|
"server.connected" | "server.heartbeat" | "session.updated"
|
||||||
|
| "session.diff" | "file.watcher.updated" => {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
vec![agent_unparsed("opencode", &format!("unknown event type: {}", event_type), value.clone())]
|
||||||
|
}
|
||||||
};
|
};
|
||||||
let _ = self.record_conversions(&session_id, conversions).await;
|
let _ = self.record_conversions(&session_id, conversions).await;
|
||||||
}
|
}
|
||||||
|
|
@ -6447,7 +6526,7 @@ fn normalize_permission_mode(
|
||||||
AgentId::Claude => false,
|
AgentId::Claude => false,
|
||||||
AgentId::Codex => matches!(mode, "default" | "plan" | "bypass" | "acceptEdits"),
|
AgentId::Codex => matches!(mode, "default" | "plan" | "bypass" | "acceptEdits"),
|
||||||
AgentId::Amp => matches!(mode, "default" | "bypass"),
|
AgentId::Amp => matches!(mode, "default" | "bypass"),
|
||||||
AgentId::Opencode => matches!(mode, "default"),
|
AgentId::Opencode => matches!(mode, "default" | "bypass"),
|
||||||
AgentId::Mock => matches!(mode, "default" | "plan" | "bypass"),
|
AgentId::Mock => matches!(mode, "default" | "plan" | "bypass"),
|
||||||
};
|
};
|
||||||
if !supported {
|
if !supported {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue