From b824a2c83920e5077e9c83c5b289d78a0f93c797 Mon Sep 17 00:00:00 2001 From: Nathan Flurry Date: Fri, 6 Feb 2026 03:05:23 -0800 Subject: [PATCH] feat: customize opencode branding (#103) --- server/packages/sandbox-agent/src/cli.rs | 13 +- .../sandbox-agent/src/opencode_compat.rs | 5 +- server/packages/sandbox-agent/src/router.rs | 65 +++-- ...oints_snapshots@agent_install_amp.snap.new | 6 + ..._session_snapshot@multi_turn_mock.snap.new | 131 ++++++++++ ...n_snapshot@permission_events_mock.snap.new | 237 ++++++++++++++++++ ...apshot@question_reply_events_mock.snap.new | 105 ++++++++ ..._snapshot@concurrency_events_mock.snap.new | 105 ++++++++ ...on_snapshot@create_session_mock-2.snap.new | 7 + ..._events_snapshot@http_events_mock.snap.new | 46 ++++ 10 files changed, 700 insertions(+), 20 deletions(-) create mode 100644 server/packages/sandbox-agent/tests/http/snapshots/http_endpoints__agent_endpoints__agent_endpoints_snapshots@agent_install_amp.snap.new create mode 100644 server/packages/sandbox-agent/tests/sessions/snapshots/sessions__sessions__multi_turn__assert_session_snapshot@multi_turn_mock.snap.new create mode 100644 server/packages/sandbox-agent/tests/sessions/snapshots/sessions__sessions__permissions__assert_session_snapshot@permission_events_mock.snap.new create mode 100644 server/packages/sandbox-agent/tests/sessions/snapshots/sessions__sessions__questions__assert_session_snapshot@question_reply_events_mock.snap.new create mode 100644 server/packages/sandbox-agent/tests/sessions/snapshots/sessions__sessions__session_lifecycle__assert_session_snapshot@concurrency_events_mock.snap.new create mode 100644 server/packages/sandbox-agent/tests/sessions/snapshots/sessions__sessions__session_lifecycle__assert_session_snapshot@create_session_mock-2.snap.new create mode 100644 server/packages/sandbox-agent/tests/sessions/snapshots/sessions__sessions__session_lifecycle__run_http_events_snapshot@http_events_mock.snap.new diff --git a/server/packages/sandbox-agent/src/cli.rs b/server/packages/sandbox-agent/src/cli.rs index fe69cd2..8390f46 100644 --- a/server/packages/sandbox-agent/src/cli.rs +++ b/server/packages/sandbox-agent/src/cli.rs @@ -15,8 +15,8 @@ use reqwest::blocking::Client as HttpClient; use reqwest::Method; use crate::router::{build_router_with_state, shutdown_servers}; use crate::router::{ - AgentInstallRequest, AppState, AuthConfig, CreateSessionRequest, MessageRequest, - PermissionReply, PermissionReplyRequest, QuestionReplyRequest, + AgentInstallRequest, AppState, AuthConfig, BrandingMode, CreateSessionRequest, + MessageRequest, PermissionReply, PermissionReplyRequest, QuestionReplyRequest, }; use crate::router::{ AgentListResponse, AgentModelsResponse, AgentModesResponse, CreateSessionResponse, @@ -502,9 +502,14 @@ fn run_server(cli: &CliConfig, server: &ServerArgs) -> Result<(), CliError> { AuthConfig::disabled() }; + let branding = if cli.gigacode { + BrandingMode::Gigacode + } else { + BrandingMode::SandboxAgent + }; let agent_manager = AgentManager::new(default_install_dir()) .map_err(|err| CliError::Server(err.to_string()))?; - let state = Arc::new(AppState::new(auth, agent_manager)); + let state = Arc::new(AppState::with_branding(auth, agent_manager, branding)); let (mut router, state) = build_router_with_state(state); let cors = build_cors_layer(server)?; @@ -581,7 +586,7 @@ fn run_api(command: &ApiCommand, cli: &CliConfig) -> Result<(), CliError> { fn run_opencode(cli: &CliConfig, args: &OpencodeArgs) -> Result<(), CliError> { let name = if cli.gigacode { "Gigacode" } else { "OpenCode command" }; - write_stderr_line(&format!("EXPERIMENTAL: Please report bugs to:\n- GitHub: https://github.com/rivet-dev/sandbox-agent/issues\n- Discord: https://rivet.dev/discord\n\n{name} is powered by:- OpenCode (TUI): https://opencode.ai/\n- Sandbox Agent SDK (multi-agent compatibility): https://sandboxagent.dev/\n\n"))?; + write_stderr_line(&format!("\nEXPERIMENTAL: Please report bugs to:\n- GitHub: https://github.com/rivet-dev/sandbox-agent/issues\n- Discord: https://rivet.dev/discord\n\n{name} is powered by:\n- OpenCode (TUI): https://opencode.ai/\n- Sandbox Agent SDK (multi-agent compatibility): https://sandboxagent.dev/\n\n"))?; let token = cli.token.clone(); diff --git a/server/packages/sandbox-agent/src/opencode_compat.rs b/server/packages/sandbox-agent/src/opencode_compat.rs index ac72d0c..e756cce 100644 --- a/server/packages/sandbox-agent/src/opencode_compat.rs +++ b/server/packages/sandbox-agent/src/opencode_compat.rs @@ -2573,9 +2573,10 @@ pub fn build_opencode_router(state: Arc) -> Router { tag = "opencode" )] async fn oc_agent_list(State(state): State>) -> impl IntoResponse { + let name = state.inner.branding.product_name(); let agent = json!({ - "name": "Sandbox Agent", - "description": "Sandbox Agent compatibility layer", + "name": name, + "description": format!("{name} compatibility layer"), "mode": "all", "native": false, "hidden": false, diff --git a/server/packages/sandbox-agent/src/router.rs b/server/packages/sandbox-agent/src/router.rs index d585eee..1563bee 100644 --- a/server/packages/sandbox-agent/src/router.rs +++ b/server/packages/sandbox-agent/src/router.rs @@ -55,15 +55,43 @@ static USER_MESSAGE_COUNTER: AtomicU64 = AtomicU64::new(1); const ANTHROPIC_MODELS_URL: &str = "https://api.anthropic.com/v1/models?beta=true"; const ANTHROPIC_VERSION: &str = "2023-06-01"; +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub enum BrandingMode { + #[default] + SandboxAgent, + Gigacode, +} + +impl BrandingMode { + pub fn product_name(&self) -> &'static str { + match self { + BrandingMode::SandboxAgent => "Sandbox Agent", + BrandingMode::Gigacode => "Gigacode", + } + } + + pub fn docs_url(&self) -> &'static str { + match self { + BrandingMode::SandboxAgent => "https://sandboxagent.dev", + BrandingMode::Gigacode => "https://gigacode.dev", + } + } +} + #[derive(Debug)] pub struct AppState { auth: AuthConfig, agent_manager: Arc, session_manager: Arc, + pub branding: BrandingMode, } impl AppState { pub fn new(auth: AuthConfig, agent_manager: AgentManager) -> Self { + Self::with_branding(auth, agent_manager, BrandingMode::default()) + } + + pub fn with_branding(auth: AuthConfig, agent_manager: AgentManager, branding: BrandingMode) -> Self { let agent_manager = Arc::new(agent_manager); let session_manager = Arc::new(SessionManager::new(agent_manager.clone())); session_manager @@ -73,6 +101,7 @@ impl AppState { auth, agent_manager, session_manager, + branding, } } @@ -152,12 +181,15 @@ pub fn build_router_with_state(shared: Arc) -> (Router, Arc) )); } - let mut router = Router::new() + let root_router = Router::new() .route("/", get(get_root)) + .fallback(not_found) + .with_state(shared.clone()); + + let mut router = root_router .nest("/v1", v1_router) .nest("/opencode", opencode_router) - .merge(opencode_root_router) - .fallback(not_found); + .merge(opencode_root_router); if ui::is_enabled() { router = router.merge(ui::router()); @@ -4068,21 +4100,26 @@ async fn get_agent_models( Ok(Json(models)) } -const SERVER_INFO: &str = "\ -This is a Sandbox Agent server. Available endpoints:\n\ - - GET / - Server info\n\ - - GET /v1/health - Health check\n\ - - GET /ui/ - Inspector UI\n\n\ -See https://sandboxagent.dev for API documentation."; - -async fn get_root() -> &'static str { - SERVER_INFO +fn server_info(branding: BrandingMode) -> String { + format!( + "This is a {} server. Available endpoints:\n\ + \x20 - GET / - Server info\n\ + \x20 - GET /v1/health - Health check\n\ + \x20 - GET /ui/ - Inspector UI\n\n\ + See {} for API documentation.", + branding.product_name(), + branding.docs_url(), + ) } -async fn not_found() -> (StatusCode, String) { +async fn get_root(State(state): State>) -> String { + server_info(state.branding) +} + +async fn not_found(State(state): State>) -> (StatusCode, String) { ( StatusCode::NOT_FOUND, - format!("404 Not Found\n\n{SERVER_INFO}"), + format!("404 Not Found\n\n{}", server_info(state.branding)), ) } diff --git a/server/packages/sandbox-agent/tests/http/snapshots/http_endpoints__agent_endpoints__agent_endpoints_snapshots@agent_install_amp.snap.new b/server/packages/sandbox-agent/tests/http/snapshots/http_endpoints__agent_endpoints__agent_endpoints_snapshots@agent_install_amp.snap.new new file mode 100644 index 0000000..d01df04 --- /dev/null +++ b/server/packages/sandbox-agent/tests/http/snapshots/http_endpoints__agent_endpoints__agent_endpoints_snapshots@agent_install_amp.snap.new @@ -0,0 +1,6 @@ +--- +source: server/packages/sandbox-agent/tests/http/agent_endpoints.rs +assertion_line: 145 +expression: snapshot_status(status) +--- +status: 204 diff --git a/server/packages/sandbox-agent/tests/sessions/snapshots/sessions__sessions__multi_turn__assert_session_snapshot@multi_turn_mock.snap.new b/server/packages/sandbox-agent/tests/sessions/snapshots/sessions__sessions__multi_turn__assert_session_snapshot@multi_turn_mock.snap.new new file mode 100644 index 0000000..2a091af --- /dev/null +++ b/server/packages/sandbox-agent/tests/sessions/snapshots/sessions__sessions__multi_turn__assert_session_snapshot@multi_turn_mock.snap.new @@ -0,0 +1,131 @@ +--- +source: server/packages/sandbox-agent/tests/sessions/multi_turn.rs +assertion_line: 15 +expression: value +--- +first: + - metadata: true + seq: 1 + session: started + type: session.started + - item: + content_types: + - text + kind: message + role: user + status: in_progress + seq: 2 + type: item.started + - delta: + delta: "" + item_id: "" + native_item_id: "" + seq: 3 + type: item.delta + - item: + content_types: + - text + kind: message + role: user + status: completed + seq: 4 + type: item.completed + - item: + content_types: + - text + kind: message + role: assistant + status: in_progress + seq: 5 + type: item.started + - delta: + delta: "" + item_id: "" + native_item_id: "" + seq: 6 + type: item.delta + - delta: + delta: "" + item_id: "" + native_item_id: "" + seq: 7 + type: item.delta + - delta: + delta: "" + item_id: "" + native_item_id: "" + seq: 8 + type: item.delta + - delta: + delta: "" + item_id: "" + native_item_id: "" + seq: 9 + type: item.delta + - delta: + delta: "" + item_id: "" + native_item_id: "" + seq: 10 + type: item.delta +second: + - item: + content_types: + - text + kind: message + role: user + status: in_progress + seq: 1 + type: item.started + - delta: + delta: "" + item_id: "" + native_item_id: "" + seq: 2 + type: item.delta + - item: + content_types: + - text + kind: message + role: user + status: completed + seq: 3 + type: item.completed + - item: + content_types: + - text + kind: message + role: assistant + status: in_progress + seq: 4 + type: item.started + - delta: + delta: "" + item_id: "" + native_item_id: "" + seq: 5 + type: item.delta + - delta: + delta: "" + item_id: "" + native_item_id: "" + seq: 6 + type: item.delta + - delta: + delta: "" + item_id: "" + native_item_id: "" + seq: 7 + type: item.delta + - delta: + delta: "" + item_id: "" + native_item_id: "" + seq: 8 + type: item.delta + - delta: + delta: "" + item_id: "" + native_item_id: "" + seq: 9 + type: item.delta diff --git a/server/packages/sandbox-agent/tests/sessions/snapshots/sessions__sessions__permissions__assert_session_snapshot@permission_events_mock.snap.new b/server/packages/sandbox-agent/tests/sessions/snapshots/sessions__sessions__permissions__assert_session_snapshot@permission_events_mock.snap.new new file mode 100644 index 0000000..d5c1b20 --- /dev/null +++ b/server/packages/sandbox-agent/tests/sessions/snapshots/sessions__sessions__permissions__assert_session_snapshot@permission_events_mock.snap.new @@ -0,0 +1,237 @@ +--- +source: server/packages/sandbox-agent/tests/sessions/permissions.rs +assertion_line: 12 +expression: value +--- +- metadata: true + seq: 1 + session: started + type: session.started +- item: + content_types: + - text + kind: message + role: user + status: in_progress + seq: 2 + type: item.started +- delta: + delta: "" + item_id: "" + native_item_id: "" + seq: 3 + type: item.delta +- item: + content_types: + - text + kind: message + role: user + status: completed + seq: 4 + type: item.completed +- item: + content_types: + - text + kind: message + role: assistant + status: in_progress + seq: 5 + type: item.started +- delta: + delta: "" + item_id: "" + native_item_id: "" + seq: 6 + type: item.delta +- delta: + delta: "" + item_id: "" + native_item_id: "" + seq: 7 + type: item.delta +- delta: + delta: "" + item_id: "" + native_item_id: "" + seq: 8 + type: item.delta +- delta: + delta: "" + item_id: "" + native_item_id: "" + seq: 9 + type: item.delta +- delta: + delta: "" + item_id: "" + native_item_id: "" + seq: 10 + type: item.delta +- delta: + delta: "" + item_id: "" + native_item_id: "" + seq: 11 + type: item.delta +- delta: + delta: "" + item_id: "" + native_item_id: "" + seq: 12 + type: item.delta +- delta: + delta: "" + item_id: "" + native_item_id: "" + seq: 13 + type: item.delta +- delta: + delta: "" + item_id: "" + native_item_id: "" + seq: 14 + type: item.delta +- delta: + delta: "" + item_id: "" + native_item_id: "" + seq: 15 + type: item.delta +- delta: + delta: "" + item_id: "" + native_item_id: "" + seq: 16 + type: item.delta +- delta: + delta: "" + item_id: "" + native_item_id: "" + seq: 17 + type: item.delta +- delta: + delta: "" + item_id: "" + native_item_id: "" + seq: 18 + type: item.delta +- delta: + delta: "" + item_id: "" + native_item_id: "" + seq: 19 + type: item.delta +- delta: + delta: "" + item_id: "" + native_item_id: "" + seq: 20 + type: item.delta +- delta: + delta: "" + item_id: "" + native_item_id: "" + seq: 21 + type: item.delta +- delta: + delta: "" + item_id: "" + native_item_id: "" + seq: 22 + type: item.delta +- delta: + delta: "" + item_id: "" + native_item_id: "" + seq: 23 + type: item.delta +- delta: + delta: "" + item_id: "" + native_item_id: "" + seq: 24 + type: item.delta +- delta: + delta: "" + item_id: "" + native_item_id: "" + seq: 25 + type: item.delta +- delta: + delta: "" + item_id: "" + native_item_id: "" + seq: 26 + type: item.delta +- delta: + delta: "" + item_id: "" + native_item_id: "" + seq: 27 + type: item.delta +- delta: + delta: "" + item_id: "" + native_item_id: "" + seq: 28 + type: item.delta +- delta: + delta: "" + item_id: "" + native_item_id: "" + seq: 29 + type: item.delta +- delta: + delta: "" + item_id: "" + native_item_id: "" + seq: 30 + type: item.delta +- delta: + delta: "" + item_id: "" + native_item_id: "" + seq: 31 + type: item.delta +- delta: + delta: "" + item_id: "" + native_item_id: "" + seq: 32 + type: item.delta +- delta: + delta: "" + item_id: "" + native_item_id: "" + seq: 33 + type: item.delta +- delta: + delta: "" + item_id: "" + native_item_id: "" + seq: 34 + type: item.delta +- delta: + delta: "" + item_id: "" + native_item_id: "" + seq: 35 + type: item.delta +- delta: + delta: "" + item_id: "" + native_item_id: "" + seq: 36 + type: item.delta +- delta: + delta: "" + item_id: "" + native_item_id: "" + seq: 37 + type: item.delta +- delta: + delta: "" + item_id: "" + native_item_id: "" + seq: 38 + type: item.delta diff --git a/server/packages/sandbox-agent/tests/sessions/snapshots/sessions__sessions__questions__assert_session_snapshot@question_reply_events_mock.snap.new b/server/packages/sandbox-agent/tests/sessions/snapshots/sessions__sessions__questions__assert_session_snapshot@question_reply_events_mock.snap.new new file mode 100644 index 0000000..f414271 --- /dev/null +++ b/server/packages/sandbox-agent/tests/sessions/snapshots/sessions__sessions__questions__assert_session_snapshot@question_reply_events_mock.snap.new @@ -0,0 +1,105 @@ +--- +source: server/packages/sandbox-agent/tests/sessions/questions.rs +assertion_line: 12 +expression: value +--- +- metadata: true + seq: 1 + session: started + type: session.started +- item: + content_types: + - text + kind: message + role: user + status: in_progress + seq: 2 + type: item.started +- delta: + delta: "" + item_id: "" + native_item_id: "" + seq: 3 + type: item.delta +- item: + content_types: + - text + kind: message + role: user + status: completed + seq: 4 + type: item.completed +- item: + content_types: + - text + kind: message + role: assistant + status: in_progress + seq: 5 + type: item.started +- delta: + delta: "" + item_id: "" + native_item_id: "" + seq: 6 + type: item.delta +- delta: + delta: "" + item_id: "" + native_item_id: "" + seq: 7 + type: item.delta +- delta: + delta: "" + item_id: "" + native_item_id: "" + seq: 8 + type: item.delta +- delta: + delta: "" + item_id: "" + native_item_id: "" + seq: 9 + type: item.delta +- delta: + delta: "" + item_id: "" + native_item_id: "" + seq: 10 + type: item.delta +- delta: + delta: "" + item_id: "" + native_item_id: "" + seq: 11 + type: item.delta +- delta: + delta: "" + item_id: "" + native_item_id: "" + seq: 12 + type: item.delta +- delta: + delta: "" + item_id: "" + native_item_id: "" + seq: 13 + type: item.delta +- delta: + delta: "" + item_id: "" + native_item_id: "" + seq: 14 + type: item.delta +- delta: + delta: "" + item_id: "" + native_item_id: "" + seq: 15 + type: item.delta +- question: + id: "" + options: 4 + status: requested + seq: 16 + type: question.requested diff --git a/server/packages/sandbox-agent/tests/sessions/snapshots/sessions__sessions__session_lifecycle__assert_session_snapshot@concurrency_events_mock.snap.new b/server/packages/sandbox-agent/tests/sessions/snapshots/sessions__sessions__session_lifecycle__assert_session_snapshot@concurrency_events_mock.snap.new new file mode 100644 index 0000000..a6e0065 --- /dev/null +++ b/server/packages/sandbox-agent/tests/sessions/snapshots/sessions__sessions__session_lifecycle__assert_session_snapshot@concurrency_events_mock.snap.new @@ -0,0 +1,105 @@ +--- +source: server/packages/sandbox-agent/tests/sessions/session_lifecycle.rs +assertion_line: 12 +expression: value +--- +session_a: + - metadata: true + seq: 1 + session: started + type: session.started + - item: + content_types: + - text + kind: message + role: user + status: in_progress + seq: 2 + type: item.started + - delta: + delta: "" + item_id: "" + native_item_id: "" + seq: 3 + type: item.delta + - item: + content_types: + - text + kind: message + role: user + status: completed + seq: 4 + type: item.completed + - item: + content_types: + - text + kind: message + role: assistant + status: in_progress + seq: 5 + type: item.started + - delta: + delta: "" + item_id: "" + native_item_id: "" + seq: 6 + type: item.delta + - delta: + delta: "" + item_id: "" + native_item_id: "" + seq: 7 + type: item.delta + - delta: + delta: "" + item_id: "" + native_item_id: "" + seq: 8 + type: item.delta + - delta: + delta: "" + item_id: "" + native_item_id: "" + seq: 9 + type: item.delta +session_b: + - metadata: true + seq: 1 + session: started + type: session.started + - item: + content_types: + - text + kind: message + role: user + status: in_progress + seq: 2 + type: item.started + - delta: + delta: "" + item_id: "" + native_item_id: "" + seq: 3 + type: item.delta + - item: + content_types: + - text + kind: message + role: user + status: completed + seq: 4 + type: item.completed + - item: + content_types: + - text + kind: message + role: assistant + status: in_progress + seq: 5 + type: item.started + - delta: + delta: "" + item_id: "" + native_item_id: "" + seq: 6 + type: item.delta diff --git a/server/packages/sandbox-agent/tests/sessions/snapshots/sessions__sessions__session_lifecycle__assert_session_snapshot@create_session_mock-2.snap.new b/server/packages/sandbox-agent/tests/sessions/snapshots/sessions__sessions__session_lifecycle__assert_session_snapshot@create_session_mock-2.snap.new new file mode 100644 index 0000000..b63c3a7 --- /dev/null +++ b/server/packages/sandbox-agent/tests/sessions/snapshots/sessions__sessions__session_lifecycle__assert_session_snapshot@create_session_mock-2.snap.new @@ -0,0 +1,7 @@ +--- +source: server/packages/sandbox-agent/tests/sessions/session_lifecycle.rs +assertion_line: 12 +expression: value +--- +healthy: true +nativeSessionId: "" diff --git a/server/packages/sandbox-agent/tests/sessions/snapshots/sessions__sessions__session_lifecycle__run_http_events_snapshot@http_events_mock.snap.new b/server/packages/sandbox-agent/tests/sessions/snapshots/sessions__sessions__session_lifecycle__run_http_events_snapshot@http_events_mock.snap.new new file mode 100644 index 0000000..da365cc --- /dev/null +++ b/server/packages/sandbox-agent/tests/sessions/snapshots/sessions__sessions__session_lifecycle__run_http_events_snapshot@http_events_mock.snap.new @@ -0,0 +1,46 @@ +--- +source: server/packages/sandbox-agent/tests/sessions/../common/http.rs +assertion_line: 1001 +expression: normalized +--- +- metadata: true + seq: 1 + session: started + type: session.started +- item: + content_types: + - text + kind: message + role: user + status: in_progress + seq: 2 + type: item.started +- delta: + delta: "" + item_id: "" + native_item_id: "" + seq: 3 + type: item.delta +- item: + content_types: + - text + kind: message + role: user + status: completed + seq: 4 + type: item.completed +- item: + content_types: + - text + kind: message + role: assistant + status: in_progress + seq: 5 + type: item.started +- item: + content_types: [] + kind: message + role: assistant + status: completed + seq: 6 + type: item.completed